<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/%E6%AD%A6%E6%B1%8932x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/%E6%AD%A6%E6%B1%8916x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib1/font-awesome/css/all.min.css">
  <link rel="stylesheet" href="/lib1/pace/pace-theme-center-simple.min.css">
  <script src="/lib1/pace/pace.min.js"></script>

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":true,"show_result":true,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
  </script>

  <meta property="og:type" content="website">
<meta property="og:title" content="Technological Blog">
<meta property="og:url" content="http://example.com/page/3/index.html">
<meta property="og:site_name" content="Technological Blog">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="Li Yudong">
<meta name="twitter:card" content="summary">

<link rel="canonical" href="http://example.com/page/3/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>

  <title>Technological Blog</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">Technological Blog</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">IT小白的成长之旅</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="tags fa-fw"></i>标签<span class="badge">18</span></a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="th fa-fw"></i>分类<span class="badge">14</span></a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="archive fa-fw"></i>归档<span class="badge">95</span></a>

  </li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div id="search-result">
  <div id="no-result">
    <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
  </div>
</div>

    </div>
  </div>

</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

  <a href="https://github.com/tiarmor1" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content index posts-expand">
            
      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/31/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%8C%EF%BC%89/" class="post-title-link" itemprop="url">JavaScript的设计模式（二）</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-31 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-31T12:18:39+08:00">2021-10-31</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:15:31" itemprop="dateModified" datetime="2021-11-03T16:15:31+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">设计模式</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。</p>
<h1 id="设计模式原则"><a href="#设计模式原则" class="headerlink" title="设计模式原则"></a>设计模式原则</h1><h2 id="单一职责原则"><a href="#单一职责原则" class="headerlink" title="单一职责原则"></a>单一职责原则</h2><p><strong>单一职责原则(Single Responsibility Principle,SRP)：一个类只负责一个功能领域中的相应职责，或者可以定义为：就一个类而言，应该只有一个引起它变化的原因。</strong> 单一职责原则是实现高内聚、低耦合的指导方针，它是最简单但又最难运用的原则，需要设计人员发现类的不同职责并将其分离，而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。 </p>
<h2 id="开放封闭原则"><a href="#开放封闭原则" class="headerlink" title="开放封闭原则"></a>开放封闭原则</h2><p> <strong>开闭原则(Open-Closed Principle,OCP)：一个软件实体应当对扩展开放，对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。</strong> 开闭原则是面向对象的可复用设计的第一块基石，它是最重要的面向对象设计原则。在开闭原则的定义中，软件实体可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。 </p>
<p>为了满足开闭原则，需要对系统进行抽象化设计，抽象化是开闭原则的关键。在Java、C#等编程语言中，可以为系统定义一个相对稳定的抽象层，而将不同的实现行为移至具体的实现层中完成。在很多面向对象编程语言中都提供了接口、抽象类等机制，可以通过它们定义系统的抽象层，再通过具体类来进行扩展。如果需要修改系统的行为，无须对抽象层进行任何改动，只需要增加新的具体类来实现新的业务功能即可，实现在不修改已有代码的基础上扩展系统的功能，达到开闭原则的要求。 </p>
<p>因为xml和properties等格式的配置文件是纯文本文件，可以直接通过编辑器进行编辑，且无须编译，因此在软件开发中，一般不把对配置文件的修改认为是对系统源代码的修改。如果一个系统在扩展时只涉及到修改配置文件，而原有的Java代码或C#代码没有做任何修改，该系统即可认为是一个符合开闭原则的系统。 </p>
<h2 id="里氏代换原则"><a href="#里氏代换原则" class="headerlink" title="里氏代换原则"></a>里氏代换原则</h2><p><strong>里氏代换原则(Liskov Substitution Principle,LSP)：所有引用基类（父类）的地方必须能透明地使用其子类的对象。 正式由于子类型的可替换性，才使得使用父类类型的模块在无需修改的情况下就可以进行扩展。</strong></p>
<p>里氏代换原则是实现开闭原则的重要方式之一，由于使用基类对象的地方都可以使用子类对象，因此在程序中尽量使用基类类型来对对象进行定义，而在运行时再确定其子类类型，用子类对象来替换父类对象。在使用里氏代换原则时需要注意如下几个问题： </p>
<p>(1)：子类的所有方法必须在父类中声明，或子类必须实现父类中声明的所有方法。根据里氏代换原则，为了保证系统的扩展性，在程序中通常使用父类来进行定义，如果一个方法只存在子类中，在父类中不提供相应的声明，则无法在以父类定义的对象中使用该方法。 </p>
<p>(2)：在运用里氏代换原则时，尽量把父类设计为抽象类或者接口，让子类继承父类或实现父接口，并实现在父类中声明的方法，运行时，子类实例替换父类实例，可以很方便地扩展系统的功能，同时无须修改原有子类的代码，增加新的功能可以通过增加一个新的子类来实现。 </p>
<h2 id="依赖倒转原则"><a href="#依赖倒转原则" class="headerlink" title="依赖倒转原则"></a>依赖倒转原则</h2><p><strong>依赖倒转原则(Dependency Inversion Principle,DIP)：抽象不应该依赖于细节，细节应当依赖于抽象。换言之，要针对接口编程，而不是针对实现编程。（高层模块不应该依赖低层模块，两个都应该依赖抽象）。</strong></p>
<p>依赖倒转原则是面向对象设计的主要实现机制之一，它是系统抽象化的具体实现。 依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中，尽量引用层次高的抽象层类，即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明，以及数据类型的转换等，而不要用具体类来做这些事情。</p>
<p>为了确保该原则的应用，一个具体类应当只实现接口或抽象类中声明过的方法，而不要给出多余的方法，否则将无法调用到在子类中增加的新方法。 在实现依赖倒转原则时，需要针对抽象层编程，而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中，依赖注入是指当一个对象要与其他对象发生依赖关系时，通过抽象来注入所依赖的对象。</p>
<p>常用的注入方式有三种，分别是：构造注入，设值注入（Setter注入）和接口注入。构造注入是指通过构造函数来传入具体类的对象，设值注入是指通过Setter方法来传入具体类的对象，而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型，在运行时再传入具体类型的对象，由子类对象来覆盖父类对象。 依赖倒转其实可以说是面向对象设计的标志，如果编写的时候考虑的都是如何针对抽象编程而不是针对细节编程，即程序中所有的依赖关系都是终止于抽象类或者接口，则为面向对象的设计。 </p>
<h2 id="接口隔离原则"><a href="#接口隔离原则" class="headerlink" title="接口隔离原则"></a>接口隔离原则</h2><p><strong>接口隔离原则(Interface Segregation Principle,ISP)：使用多个专门的接口，而不使用单一的总接口，即客户端不应该依赖那些它不需要的接口。</strong> </p>
<p>根据接口隔离原则，当一个接口太大时，我们需要将它分割成一些更细小的接口，使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色，不干不该干的事，该干的事都要干。这里的“接口”往往有两种不同的含义：一种是指一个类型所具有的方法特征的集合，仅仅是一种逻辑上的抽象；另外一种是指某种语言具体的“接口”定义，有严格的定义和结构，比如Java语言中的interface。对于这两种不同的含义，ISP的表达方式以及含义都有所不同： </p>
<p>(1)： 当把“接口”理解成一个类型所提供的所有方法特征的集合的时候，这就是一种逻辑上的概念，接口的划分将直接带来类型的划分。可以把接口理解成角色，一个接口只能代表一个角色，每个角色都有它特定的一个接口，此时，这个原则可以叫做“角色隔离原则”。</p>
<p>(2)： 如果把“接口”理解成狭义的特定语言的接口，那么ISP表达的意思是指接口仅仅提供客户端需要的行为，客户端不需要的行为则隐藏起来，应当为客户端提供尽可能小的单独的接口，而不要提供大的总接口，比如实现中还有还有自己的一些封装的功能方法，而对于每一个接口实现类，可能这些功能代码可能不同。在面向对象编程语言中，实现一个接口就需要实现该接口中定义的所有方法，因此大的总接口使用起来不一定很方便，为了使接口的职责单一，需要将大接口中的方法根据其职责不同分别放在不同的小接口中，以确保每个接口使用起来都较为方便，并都承担某一单一角色。</p>
<p>接口应该尽量细化，同时接口中的方法应该尽量少，每个接口中只包含一个客户端（如子模块或业务逻辑类）所需的方法即可，这种机制也称为“定制服务”，即为不同的客户端提供宽窄不同的接口。 在使用接口隔离原则时，需要注意控制接口的粒度，接口不能太小，如果太小会导致系统中接口泛滥，不利于维护；接口也不能太大，太大的接口将违背接口隔离原则，灵活性较差，使用起来很不方便。 </p>
<h2 id="迪米特法则"><a href="#迪米特法则" class="headerlink" title="迪米特法则"></a>迪米特法则</h2><p><strong>迪米特法则(Law of Demeter,LoD)：又叫作最少知道原则（Least Knowledge Principle, LKP），一个软件实体应当尽可能少地与其他实体发生相互作用。</strong>就是说一个对象应当对其他对象有尽可能少的了解，不和陌生人说话。 如果两个类不必彼此直接通信，那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话，可以通过第三者转发这个调用。 </p>
<p>迪米特法则可降低系统的耦合度，使类与类之间保持松散的耦合关系。迪米特法则可以简单说成：talk only to your immediate friends。 对于LOD来说，又被解释为下面几种方式：一个软件实体应当尽可能少的与其他实体发生相互作用。每一个软件单位对其他的单位都只有最少的知识，而且局限于那些与本单位密切相关的软件单位。 迪米特法则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖，因此，很容易使得系统的功能模块功能独立，相互之间不存在（或很少有）依赖关系。 </p>
<p>迪米特法则不希望类之间建立直接的联系。如果真的有需要建立联系，也希望能通过它的友元类来转达。因此，应用迪米特法则有可能造成的一个后果就是：系统中存在大量的中介类，这些类之所以存在完全是为了传递类之间的相互调用关系——这在一定程度上增加了系统的复杂度。 设计模式的门面模式（Facade）和中介模式（Mediator），都是应用的例子。 </p>
<h1 id="迭代器模式"><a href="#迭代器模式" class="headerlink" title="迭代器模式"></a>迭代器模式</h1><p>迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素，而又不需要暴露该对象 的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来，在使用迭代器模式之后，即 使不关心对象的内部构造，也可以按顺序访问其中的每个元素。</p>
<p>现在我们来自己实现一个 each 函数，each 函数接受 2 个参数，第一个为被循环的数组，第 二个为循环中的每一步后将被触发的回调函数：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> each = <span class="function"><span class="keyword">function</span>(<span class="params"> ary, callback </span>)</span>&#123;</span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = ary.length; i &lt; l; i++ )&#123; </span><br><span class="line">        callback.call( ary[i], i, ary[ i ] ); <span class="comment">// 把下标和元素当作参数传给 callback 函数</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">each( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>&#123; </span><br><span class="line">    alert ( [ i, n ] ); </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="内部、外部迭代器"><a href="#内部、外部迭代器" class="headerlink" title="内部、外部迭代器"></a>内部、外部迭代器</h2><h3 id="内部迭代"><a href="#内部迭代" class="headerlink" title="内部迭代"></a>内部迭代</h3><p>我们刚刚编写的 each 函数属于内部迭代器，each 函数的内部已经定义好了迭代规则，它完 全接手整个迭代过程，外部只需要一次初始调用。 内部迭代器在调用的时候非常方便，外界不用关心迭代器内部的实现，跟迭代器的交互也仅 仅是一次初始调用，但这也刚好是内部迭代器的缺点。由于内部迭代器的迭代规则已经被提前规 定，上面的 each 函数就无法同时迭代 2 个数组了。</p>
<p>比如现在有个需求，要判断 2 个数组里元素的值是否完全相等， 如果不改写 each 函数本身 的代码，我们能够入手的地方似乎只剩下 each 的回调函数了，代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> ary1, ary2 </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( ary1.length !== ary2.length )&#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">    each( ary1, <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( n !== ary2[ i ] )&#123; </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'ary1 和 ary2 不相等'</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;); </span><br><span class="line">    alert ( <span class="string">'ary1 和 ary2 相等'</span> ); </span><br><span class="line">&#125;; </span><br><span class="line">compare( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ], [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">4</span> ] ); <span class="comment">// throw new Error ( 'ary1 和 ary2 不相等' );</span></span><br></pre></td></tr></table></figure>

<p>说实话，这个 compare 函数一点都算不上好看，我们目前能够顺利完成需求，还要感谢在 JavaScript 里可以把函数当作参数传递的特性，但在其他语言中未必就能如此幸运。在一些没有闭包的语言中，内部迭代器本身的实现也相当复杂。比如 C 语言中的内部迭代器 是用函数指针来实现的，循环处理所需要的数据都要以参数的形式明确地从外面传递进去。</p>
<h3 id="外部迭代"><a href="#外部迭代" class="headerlink" title="外部迭代"></a>外部迭代</h3><p>外部迭代器必须显式地请求迭代下一个元素。 外部迭代器增加了一些调用的复杂度，但相对也增强了迭代器的灵活性，我们可以手工控制 迭代的过程或者顺序。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Iterator = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> current = <span class="number">0</span>; </span><br><span class="line">    <span class="keyword">var</span> next = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        current += <span class="number">1</span>; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> isDone = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> current &gt;= obj.length; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">var</span> getCurrItem = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> obj[ current ]; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        next: next, </span><br><span class="line">        isDone: isDone, </span><br><span class="line">        getCurrItem: getCurrItem </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">//再看看该如何改写compare函数</span></span><br><span class="line"><span class="keyword">var</span> compare = <span class="function"><span class="keyword">function</span>(<span class="params"> iterator1, iterator2 </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">while</span>( !iterator1.isDone() &amp;&amp; !iterator2.isDone() )&#123; </span><br><span class="line">        <span class="keyword">if</span> ( iterator1.getCurrItem() !== iterator2.getCurrItem() )&#123; </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span> ( <span class="string">'iterator1 和 iterator2 不相等'</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">        iterator1.next(); </span><br><span class="line">        iterator2.next(); </span><br><span class="line">    &#125; </span><br><span class="line">    alert ( <span class="string">'iterator1 和 iterator2 相等'</span> ); </span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">var</span> iterator1 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] ); </span><br><span class="line"><span class="keyword">var</span> iterator2 = Iterator( [ <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ] ); </span><br><span class="line">compare( iterator1, iterator2 ); <span class="comment">// 输出：iterator1 和 iterator2 相等</span></span><br></pre></td></tr></table></figure>

<p>外部迭代器虽然调用方式相对复杂，但它的适用面更广，也能满足更多变的需求。内部迭代 器和外部迭代器在实际生产中没有优劣之分，究竟使用哪个要根据需求场景而定。</p>
<h2 id="迭代器应用"><a href="#迭代器应用" class="headerlink" title="迭代器应用"></a>迭代器应用</h2><p>在不同的浏览器环境下，选择的上传方式是不一样的。因为使用浏览器的上传控件进行上传 速度快，可以暂停和续传，所以我们首先会优先使用控件上传。如果浏览器没有安装上传控件， 则使用 Flash 上传， 如果连 Flash 也没安装，那就只好使用浏览器原生的表单上传了。</p>
<p>为了得到一个 upload 对象，这个 getUploadObj 函数里面充斥了 try，catch 以及 if 条件分支。缺点是显而易见的。第一是很难阅读，第二是严重违反开闭原则。 在开发和 调试过程中，我们需要来回切换不同的上传方式，每次改动都相当痛苦。后来我们还增加支持了 一些另外的上传方式，比如，HTML5 上传，这时候唯一的办法是继续往 getUploadObj 函数里增 加条件分支。 </p>
<p>现在来梳理一下问题，目前一共有 3 种可能的上传方式，我们不知道目前正在使用的浏览器 支持哪几种。就好比我们有一个钥匙串，其中共有 3 把钥匙，我们想打开一扇门但是不知道该使 用哪把钥匙，于是从第一把钥匙开始，迭代钥匙串进行尝试，直到找到了正确的钥匙为止。  同样，我们把每种获取 upload 对象的方法都封装在各自的函数里，然后使用一个迭代器， 迭代获取这些 upload 对象，直到获取到一个可用的为止：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getActiveUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">try</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> ActiveXObject( <span class="string">"TXFTNActiveX.FTNUpload"</span> ); <span class="comment">// IE 上传控件</span></span><br><span class="line">    &#125;<span class="keyword">catch</span>(e)&#123; </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> getFlashUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( supportFlash() )&#123; <span class="comment">// supportFlash 函数未提供</span></span><br><span class="line">        <span class="keyword">var</span> str = <span class="string">'&lt;object type="application/x-shockwave-flash"&gt;&lt;/object&gt;'</span>; </span><br><span class="line">        <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> getFormUpladObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> str = <span class="string">'&lt;input name="file" type="file" class="ui-file"/&gt;'</span>; <span class="comment">// 表单上传</span></span><br><span class="line">    <span class="keyword">return</span> $( str ).appendTo( $(<span class="string">'body'</span>) ); </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在 getActiveUploadObj、getFlashUploadObj、getFormUpladObj 这 3 个函数中都有同一个约定： 如果该函数里面的 upload 对象是可用的，则让函数返回该对象，反之返回 false，提示迭代器继 续往后面进行迭代。 所以我们的迭代器只需进行下面这几步工作。</p>
<p>1、提供一个可以被迭代的方法，使得 getActiveUploadObj，getFlashUploadObj 以及 getFlashUploadObj 依照优先级被循环迭代。</p>
<p>2、如果正在被迭代的函数返回一个对象，则表示找到了正确的 upload 对象，反之如果该函 数返回 false，则让迭代器继续工作。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> iteratorUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = <span class="built_in">arguments</span>[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> uploadObj = fn(); </span><br><span class="line">        <span class="keyword">if</span> ( uploadObj !== <span class="literal">false</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> uploadObj; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> uploadObj = iteratorUploadObj( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">重构代码之后，我们可以看到，获取不同上传对象的方法被隔离在各自的函数里互不干扰，</span></span><br><span class="line"><span class="comment">try、catch 和 if 分支不再纠缠在一起，使得我们可以很方便地的维护和扩展代码。比如，后来</span></span><br><span class="line"><span class="comment">我们又给上传项目增加了 Webkit 控件上传和 HTML5 上传，我们要做的仅仅是下面一些工作。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="comment">//1、增加分别获取 Webkit 控件上传对象和 HTML5 上传对象的函数：</span></span><br><span class="line"><span class="keyword">var</span> getWebkitUploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">// 具体代码略</span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> getHtml5UploadObj = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">// 具体代码略</span></span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//2、依照优先级把它们添加进迭代器：</span></span><br><span class="line"><span class="keyword">var</span> uploadObj = iteratorUploadObj( getActiveUploadObj, getWebkitUploadObj, getFlashUploadObj, getHtml5UploadObj, getFormUpladObj );</span><br></pre></td></tr></table></figure>

<p>迭代器模式是一种相对简单的模式，简单到很多时候我们都不认为它是一种设计模式。目前 的绝大部分语言都内置了迭代器</p>
<h1 id="发布订阅模式"><a href="#发布订阅模式" class="headerlink" title="发布订阅模式"></a>发布订阅模式</h1><p>在刚刚的例子中，发送短信通知就是一个典型的发布—订阅模式，小明、小红等购买者都是 订阅者，他们订阅了房子开售的消息。售楼处作为发布者，会在合适的时候遍历花名册上的电话 号码，依次给购房者发布消息。可以发现，在这个例子中使用发布—订阅模式有着显而易见的优点。 </p>
<p>1、购房者不用再天天给售楼处打电话咨询开售时间，在合适的时间点，售楼处作为发布者 会通知这些消息订阅者。 2、 购房者和售楼处之间不再强耦合在一起，当有新的购房者出现时，他只需把手机号码留 在售楼处，售楼处不关心购房者的任何情况，不管购房者是男是女还是一只猴子。 而售 楼处的任何变动也不会影响购买者，比如售楼 MM 离职，售楼处从一楼搬到二楼，这些 改变都跟购房者无关，只要售楼处记得发短信这件事情</p>
<p>第一点说明发布—订阅模式可以广泛应用于异步编程中，这是一种替代传递回调函数的方案。 比如，我们可以订阅 ajax 请求的 error、succ 等事件。或者如果想在动画的每一帧完成之后做一 些事情，那我们可以订阅一个事件，然后在动画的每一帧完成之后发布这个事件。在异步编程中 使用发布—订阅模式，我们就无需过多关注对象在异步运行期间的内部状态，而只需要订阅感兴 趣的事件发生点。 </p>
<p>第二点说明发布—订阅模式可以取代对象之间硬编码的通知机制，一个对象不用再显式地调 用另外一个对象的某个接口。发布—订阅模式让两个对象松耦合地联系在一起，虽然不太清楚彼 此的细节，但这不影响它们之间相互通信。当有新的订阅者出现时，发布者的代码不需要任何修 改；同样发布者需要改变时，也不会影响到之前的订阅者。只要之前约定的事件名没有变化，就 可以自由地改变它们</p>
<p>实现发布订阅模式的步骤：</p>
<p>1、 首先要指定好谁充当发布者（比如售楼处）；2、然后给发布者添加一个缓存列表，用于存放回调函数以便通知订阅者（售楼处的花名册）；3、最后发布消息的时候，发布者会遍历这个缓存列表，依次触发里面存放的订阅者回调函 数（遍历花名册，挨个发短信）</p>
<p>另外，我们还可以往回调函数里填入一些参数，订阅者可以接收这些参数。这是很有必要的， 比如售楼处可以在发给订阅者的短信里加上房子的单价、面积、容积率等信息，订阅者接收到这 些信息之后可以进行各自的处理。下面，我们简单实现一个自己的发布订阅模式：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> salesOffices = &#123;&#125;; <span class="comment">// 定义售楼处</span></span><br><span class="line">salesOffices.clientList = &#123;&#125;; <span class="comment">// 缓存列表，存放订阅者的回调函数</span></span><br><span class="line">salesOffices.listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">if</span> ( !<span class="keyword">this</span>.clientList[ key ] )&#123; <span class="comment">// 如果还没有订阅过此类消息，给该类消息创建一个缓存列表</span></span><br><span class="line">        <span class="keyword">this</span>.clientList[ key ] = []; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">this</span>.clientList[ key ].push( fn ); <span class="comment">// 订阅的消息添加进消息缓存列表</span></span><br><span class="line">&#125;; </span><br><span class="line">salesOffices.trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; <span class="comment">// 发布消息</span></span><br><span class="line">    <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), <span class="comment">// 取出消息类型</span></span><br><span class="line">        fns = <span class="keyword">this</span>.clientList[ key ]; <span class="comment">// 取出该消息对应的回调函数集合</span></span><br><span class="line">    <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; <span class="comment">// 如果没有订阅该消息，则返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">        fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是发布消息时附送的参数</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅 88 平方米房子的消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出： 2000000 </span></span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter110'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅 110 平方米房子的消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出： 3000000 </span></span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 发布 88 平方米房子的价格</span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter110'</span>, <span class="number">3000000</span> ); <span class="comment">// 发布 110 平方米房子的价格</span></span><br></pre></td></tr></table></figure>

<p>现在我们已经看到了如何让售楼处拥有接受订阅和发布事件的功能。假设现在小明又去另一个售楼处买房子，那么这段代码是否必须在另一个售楼处对象上重写一次呢，有没有办法可以让 所有对象都拥有发布—订阅功能呢？ 答案显然是有的，JavaScript 作为一门解释执行的语言，给对象动态添加职责是理所当然的 事情。 所以我们把发布—订阅的功能提取出来，放在一个单独的对象内：</p>
<h2 id="通用实现"><a href="#通用实现" class="headerlink" title="通用实现"></a>通用实现</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//我们把发布—订阅的功能提取出来，放在一个单独的对象内：</span></span><br><span class="line"><span class="keyword">var</span> event = &#123; </span><br><span class="line">    clientList: [], </span><br><span class="line">    listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !<span class="keyword">this</span>.clientList[ key ] )&#123; </span><br><span class="line">            <span class="keyword">this</span>.clientList[ key ] = []; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">this</span>.clientList[ key ].push( fn ); <span class="comment">// 订阅的消息添加进缓存列表</span></span><br><span class="line">    &#125;, </span><br><span class="line">    trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), <span class="comment">// (1); </span></span><br><span class="line">            fns = <span class="keyword">this</span>.clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; <span class="comment">// 如果没有绑定对应的消息</span></span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">            fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); <span class="comment">// (2) // arguments 是 trigger 时带上的参数</span></span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">//再定义一个 installEvent 函数，这个函数可以给所有的对象都动态安装发布—订阅功能：关键在于这个动态添加的使用</span></span><br><span class="line"><span class="keyword">var</span> installEvent = <span class="function"><span class="keyword">function</span>(<span class="params"> obj </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i <span class="keyword">in</span> event )&#123; </span><br><span class="line">        obj[ i ] = event[ i ]; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//再来测试一番，我们给售楼处对象 salesOffices 动态增加发布—订阅功能：</span></span><br><span class="line"><span class="keyword">var</span> salesOffices = &#123;&#125;; </span><br><span class="line">installEvent( salesOffices ); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.listen( <span class="string">'squareMeter100'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;); </span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 输出：2000000 </span></span><br><span class="line">salesOffices.trigger( <span class="string">'squareMeter100'</span>, <span class="number">3000000</span> ); <span class="comment">// 输出：3000000</span></span><br></pre></td></tr></table></figure>

<p>有时候，我们也许需要取消订阅事件的功能。比如小明突然不想买房子了，为了避免继续接 收到售楼处推送过来的短信，小明需要取消之前订阅的事件。现在我们给 event 对象增加 remove 方法</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">event.remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> fns = <span class="keyword">this</span>.clientList[ key ]; </span><br><span class="line">    <span class="keyword">if</span> ( !fns )&#123; <span class="comment">// 如果 key 对应的消息没有被人订阅，则直接返回</span></span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">if</span> ( !fn )&#123; <span class="comment">// 如果没有传入具体的回调函数，表示需要取消 key 对应消息的所有订阅</span></span><br><span class="line">        fns &amp;&amp; ( fns.length = <span class="number">0</span> ); </span><br><span class="line">    &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">        <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; <span class="comment">// 反向遍历订阅的回调函数列表</span></span><br><span class="line">            <span class="keyword">var</span> _fn = fns[ l ]; </span><br><span class="line">            <span class="keyword">if</span> ( _fn === fn )&#123; </span><br><span class="line">                fns.splice( l, <span class="number">1</span> ); <span class="comment">// 删除订阅者的回调函数</span></span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="全局发布订阅"><a href="#全局发布订阅" class="headerlink" title="全局发布订阅"></a>全局发布订阅</h2><p>回想下刚刚实现的发布—订阅模式，我们给售楼处对象和登录对象都添加了订阅和发布的功 能，这里还存在两个小问题。 </p>
<p>1、 我们给每个发布者对象都添加了 listen 和 trigger 方法，以及一个缓存列表 clientList， 这其实是一种资源浪费。 </p>
<p>2、小明跟售楼处对象还是存在一定的耦合性，小明至少要知道售楼处对象的名字是 salesOffices，才能顺利的订阅到事件。见如下代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">salesOffices.listen( <span class="string">'squareMeter100'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小明订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); </span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<p>在程序中，发布—订阅模式可以用一个全局的 Event 对象来实现，订阅者不需要了解消 息来自哪个发布者，发布者也不知道消息会推送给哪些订阅者，Event 作为一个类似“中介者” 的角色，把订阅者和发布者联系起来。这也就是最常用的中介者模式：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> Event = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="comment">//为了模仿块级作用域，(function()&#123;&#125;)();定义并立即调用一个匿名函数</span></span><br><span class="line">    <span class="keyword">var</span> clientList = &#123;&#125;, </span><br><span class="line">        listen, </span><br><span class="line">        trigger, </span><br><span class="line">        remove; </span><br><span class="line">    listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !clientList[ key ] )&#123; </span><br><span class="line">            clientList[ key ] = []; </span><br><span class="line">        &#125; </span><br><span class="line">        clientList[ key ].push( fn ); </span><br><span class="line">    &#125;; </span><br><span class="line">    trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> key = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> ), </span><br><span class="line">            fns = clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns || fns.length === <span class="number">0</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">for</span>( <span class="keyword">var</span> i = <span class="number">0</span>, fn; fn = fns[ i++ ]; )&#123; </span><br><span class="line">            fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;; </span><br><span class="line">    remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> fns = clientList[ key ]; </span><br><span class="line">        <span class="keyword">if</span> ( !fns )&#123; </span><br><span class="line">            <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span> ( !fn )&#123; </span><br><span class="line">            fns &amp;&amp; ( fns.length = <span class="number">0</span> ); </span><br><span class="line">        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">            <span class="keyword">for</span> ( <span class="keyword">var</span> l = fns.length - <span class="number">1</span>; l &gt;=<span class="number">0</span>; l-- )&#123; </span><br><span class="line">                <span class="keyword">var</span> _fn = fns[ l ]; </span><br><span class="line">                <span class="keyword">if</span> ( _fn === fn )&#123; </span><br><span class="line">                    fns.splice( l, <span class="number">1</span> ); </span><br><span class="line">                &#125; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        listen: listen, </span><br><span class="line">        trigger: trigger, </span><br><span class="line">        remove: remove </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line">Event.listen( <span class="string">'squareMeter88'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> price </span>)</span>&#123; <span class="comment">// 小红订阅消息</span></span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'价格= '</span> + price ); <span class="comment">// 输出：'价格=2000000' </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.trigger( <span class="string">'squareMeter88'</span>, <span class="number">2000000</span> ); <span class="comment">// 售楼处发布消息</span></span><br></pre></td></tr></table></figure>

<h2 id="模块间通信"><a href="#模块间通信" class="headerlink" title="模块间通信"></a>模块间通信</h2><p>上一节中实现的发布—订阅模式的实现，是基于一个全局的 Event 对象，我们利用它可以在 两个封装良好的模块中进行通信，这两个模块可以完全不知道对方的存在。就如同有了中介公司 之后，我们不再需要知道房子开售的消息来自哪个售楼处。 比如现在有两个模块，a 模块里面有一个按钮，每次点击按钮之后，b 模块里的 div 中会显示 按钮的总点击次数，我们用全局发布—订阅模式完成下面的代码，使得 a 模块和 b 模块可以在保 持封装性的前提下进行通信</p>
<p>我们所了解到的发布—订阅模式，都是订阅者必须先订阅一个消息，随后才能接收到发布者 发布的消息。如果把顺序反过来，发布者先发布一条消息，而在此之前并没有对象来订阅它，这 条消息无疑将消失在宇宙中。 在某些情况下，我们需要先将这条消息保存下来，等到有对象来订阅它的时候，再重新把消 息发布给订阅者。就如同 QQ 中的离线消息一样，离线消息被保存在服务器中，接收人下次登录 上线之后，可以重新收到这条消息</p>
<p>为了满足这个需求，我们要建立一个存放离线事件的堆栈，当事件发布的时候，如果此时还 没有订阅者来订阅这个事件，我们暂时把发布事件的动作包裹在一个函数里，这些包装函数将被 存入堆栈中，等到终于有对象来订阅此事件的时候，我们将遍历堆栈并且依次执行这些包装函数， 也就是重新发布里面的事件。当然离线事件的生命周期只有一次，就像 QQ 的未读消息只会被重 新阅读一次，所以刚才的操作我们只能进行一次。</p>
<h2 id="命名冲突"><a href="#命名冲突" class="headerlink" title="命名冲突"></a>命名冲突</h2><p>全局的发布—订阅对象里只有一个 clinetList 来存放消息名和回调函数，大家都通过它来订 阅和发布各种消息，久而久之，难免会出现事件名冲突的情况，所以我们还可以给 Event 对象提 供创建命名空间的功能。 在提供最终的代码之前，我们来感受一下怎么使用这两个新增的功能。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/************** 先发布后订阅 ********************/</span> </span><br><span class="line">Event.trigger( <span class="string">'click'</span>, <span class="number">1</span> ); </span><br><span class="line">Event.listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：1 </span></span><br><span class="line">&#125;); </span><br><span class="line"><span class="comment">/************** 使用命名空间 ********************/</span> </span><br><span class="line">Event.create( <span class="string">'namespace1'</span> ).listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：1 </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.create( <span class="string">'namespace1'</span> ).trigger( <span class="string">'click'</span>, <span class="number">1</span> ); </span><br><span class="line">Event.create( <span class="string">'namespace2'</span> ).listen( <span class="string">'click'</span>, <span class="function"><span class="keyword">function</span>(<span class="params"> a </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( a ); <span class="comment">// 输出：2 </span></span><br><span class="line">&#125;); </span><br><span class="line">Event.create( <span class="string">'namespace2'</span> ).trigger( <span class="string">'click'</span>, <span class="number">2</span> );</span><br><span class="line"></span><br><span class="line"><span class="comment">//具体实现代码如下：</span></span><br><span class="line"><span class="keyword">var</span> Event = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> global = <span class="keyword">this</span>, </span><br><span class="line">        Event, </span><br><span class="line">        _default = <span class="string">'default'</span>; </span><br><span class="line">    </span><br><span class="line">    Event = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> _listen, </span><br><span class="line">            _trigger, </span><br><span class="line">            _remove, </span><br><span class="line">            _slice = <span class="built_in">Array</span>.prototype.slice, </span><br><span class="line">            _shift = <span class="built_in">Array</span>.prototype.shift, </span><br><span class="line">            _unshift = <span class="built_in">Array</span>.prototype.unshift, </span><br><span class="line">            namespaceCache = &#123;&#125;, </span><br><span class="line">            _create, </span><br><span class="line">            find, </span><br><span class="line">            each = <span class="function"><span class="keyword">function</span>(<span class="params"> ary, fn </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> ret; </span><br><span class="line">                <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = ary.length; i &lt; l; i++ )&#123;</span><br><span class="line">                    <span class="keyword">var</span> n = ary[i]; </span><br><span class="line">                    ret = fn.call( n, i, n); </span><br><span class="line">                &#125; </span><br><span class="line">                <span class="keyword">return</span> ret; </span><br><span class="line">            &#125;; </span><br><span class="line">        _listen = <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, cache </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( !cache[ key ] )&#123; </span><br><span class="line">                cache[ key ] = []; </span><br><span class="line">            &#125; </span><br><span class="line">            cache[key].push( fn ); </span><br><span class="line">        &#125;; </span><br><span class="line">        _remove = <span class="function"><span class="keyword">function</span>(<span class="params"> key, cache ,fn</span>)</span>&#123; </span><br><span class="line">            <span class="keyword">if</span> ( cache[ key ] )&#123; </span><br><span class="line">                <span class="keyword">if</span>( fn )&#123; </span><br><span class="line">                    <span class="keyword">for</span>( <span class="keyword">var</span> i = cache[ key ].length; i &gt;= <span class="number">0</span>; i-- )&#123; </span><br><span class="line">                        <span class="keyword">if</span>( cache[ key ][i] === fn )&#123; </span><br><span class="line">                            cache[ key ].splice( i, <span class="number">1</span> ); </span><br><span class="line">                        &#125; </span><br><span class="line">                    &#125; </span><br><span class="line">                &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                    cache[ key ] = []; </span><br><span class="line">                &#125; </span><br><span class="line">            &#125; </span><br><span class="line">        &#125;; </span><br><span class="line">        _trigger = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> cache = _shift.call(<span class="built_in">arguments</span>), </span><br><span class="line">                key = _shift.call(<span class="built_in">arguments</span>), </span><br><span class="line">                args = <span class="built_in">arguments</span>, </span><br><span class="line">                _self = <span class="keyword">this</span>, </span><br><span class="line">                ret, </span><br><span class="line">                stack = cache[ key ]; </span><br><span class="line">            <span class="keyword">if</span> ( !stack || !stack.length )&#123; </span><br><span class="line">                <span class="keyword">return</span>; </span><br><span class="line">            &#125; </span><br><span class="line">            <span class="keyword">return</span> each( stack, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">this</span>.apply( _self, args ); </span><br><span class="line">            &#125;); </span><br><span class="line">        &#125;; </span><br><span class="line">        _create = <span class="function"><span class="keyword">function</span>(<span class="params"> namespace </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> namespace = namespace || _default; </span><br><span class="line">            <span class="keyword">var</span> cache = &#123;&#125;, </span><br><span class="line">                offlineStack = [], <span class="comment">// 离线事件 </span></span><br><span class="line">                ret = &#123; </span><br><span class="line">                    listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                        _listen( key, fn, cache ); </span><br><span class="line">                        <span class="keyword">if</span> ( offlineStack === <span class="literal">null</span> )&#123; </span><br><span class="line">                            <span class="keyword">return</span>; </span><br><span class="line">                        &#125; </span><br><span class="line">                        <span class="keyword">if</span> ( last === <span class="string">'last'</span> )&#123;</span><br><span class="line">                            offlineStack.length &amp;&amp; offlineStack.pop()(); </span><br><span class="line">                        &#125;<span class="keyword">else</span>&#123; </span><br><span class="line">                            each( offlineStack, <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                                <span class="keyword">this</span>(); </span><br><span class="line">                            &#125;); </span><br><span class="line">                        &#125; </span><br><span class="line">                        offlineStack = <span class="literal">null</span>; </span><br><span class="line">                    &#125;, </span><br><span class="line">                    one: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                        _remove( key, cache ); </span><br><span class="line">                        <span class="keyword">this</span>.listen( key, fn ,last ); </span><br><span class="line">                    &#125;, </span><br><span class="line">                    remove: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn </span>)</span>&#123; </span><br><span class="line">                        _remove( key, cache ,fn); </span><br><span class="line">                    &#125;, </span><br><span class="line">                    trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                        <span class="keyword">var</span> fn, </span><br><span class="line">                            args, </span><br><span class="line">                            _self = <span class="keyword">this</span>; </span><br><span class="line">                        _unshift.call( <span class="built_in">arguments</span>, cache ); </span><br><span class="line">                        args = <span class="built_in">arguments</span>; </span><br><span class="line">                        fn = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                            <span class="keyword">return</span> _trigger.apply( _self, args ); </span><br><span class="line">                        &#125;; </span><br><span class="line">                        <span class="keyword">if</span> ( offlineStack )&#123; </span><br><span class="line">                            <span class="keyword">return</span> offlineStack.push( fn ); </span><br><span class="line">                        &#125; </span><br><span class="line">                        <span class="keyword">return</span> fn(); </span><br><span class="line">                    &#125; </span><br><span class="line">                &#125;; </span><br><span class="line">            <span class="keyword">return</span> namespace ? ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : </span><br><span class="line"> namespaceCache[ namespace ] = ret ) : ret; </span><br><span class="line">        &#125;; </span><br><span class="line">        <span class="keyword">return</span> &#123; </span><br><span class="line">            create: _create, </span><br><span class="line">            one: <span class="function"><span class="keyword">function</span>(<span class="params"> key,fn, last </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.one( key,fn,last ); </span><br><span class="line">            &#125;, </span><br><span class="line">            remove: <span class="function"><span class="keyword">function</span>(<span class="params"> key,fn </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.remove( key,fn ); </span><br><span class="line">            &#125;, </span><br><span class="line">            listen: <span class="function"><span class="keyword">function</span>(<span class="params"> key, fn, last </span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( ); </span><br><span class="line">                event.listen( key, fn, last ); </span><br><span class="line">            &#125;, </span><br><span class="line">            trigger: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">                <span class="keyword">var</span> event = <span class="keyword">this</span>.create( );</span><br><span class="line">                event.trigger.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">            &#125; </span><br><span class="line">        &#125;; </span><br><span class="line">    &#125;(); </span><br><span class="line">    <span class="keyword">return</span> Event; </span><br><span class="line">&#125;)();</span><br></pre></td></tr></table></figure>

<h2 id="便利性"><a href="#便利性" class="headerlink" title="便利性"></a>便利性</h2><p>我们一直讨论的发布—订阅模式，跟一些别的语言（比如 Java）中的实现 还是有区别的。在 Java 中实现一个自己的发布—订阅模式，通常会把订阅者对象自身当成引用传 入发布者对象中，同时订阅者对象还需提供一个名为诸如 update 的方法，供发布者对象在适合的 时候调用。而在 JavaScript 中，我们用注册回调函数的形式来代替传统的发布—订阅模式，显得更 加优雅和简单。 </p>
<p>另外，在 JavaScript 中，我们无需去选择使用推模型还是拉模型。推模型是指在事件发生时， 发布者一次性把所有更改的状态和数据都推送给订阅者。拉模型不同的地方是，发布者仅仅通知 订阅者事件已经发生了，此外发布者要提供一些公开的接口供订阅者来主动拉取数据。拉模型的 好处是可以让订阅者“按需获取”，但同时有可能让发布者变成一个“门户大开”的对象，同时 增加了代码量和复杂度</p>
<p>刚好在 JavaScript 中，arguments 可以很方便地表示参数列表，所以我们一般都会选择推模型， 使用 Function.prototype.apply 方法把所有参数都推送给订阅者。</p>
<p>发布—订阅模式的优点非常明显，一为时间上的解耦，二为对象之间的解耦。它的应用非常 广泛，既可以用在异步编程中，也可以帮助我们完成更松耦合的代码编写。发布—订阅模式还可 以用来帮助实现一些别的设计模式，比如中介者模式。从架构上来看，无论是 MVC 还是 MVVM， 都少不了发布—订阅模式的参与，而且 JavaScript 本身也是一门基于事件驱动的语言。  </p>
<p>当然，发布—订阅模式也不是完全没有缺点。创建订阅者本身要消耗一定的时间和内存，而 且当你订阅一个消息后，也许此消息最后都未发生，但这个订阅者会始终存在于内存中。另外， 发布—订阅模式虽然可以弱化对象之间的联系，但如果过度使用的话，对象和对象之间的必要联 系也将被深埋在背后，会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者嵌套到一 起的时候，要跟踪一个 bug 不是件轻松的事情。</p>
<h1 id="命令模式"><a href="#命令模式" class="headerlink" title="命令模式"></a>命令模式</h1><p>假设有一个快餐店，而我是该餐厅的点餐服务员，那么我一天的工作应该是这样的：当某位 客人点餐或者打来订餐电话后，我会把他的需求都写在清单上，然后交给厨房，客人不用关心是 哪些厨师帮他炒菜。我们餐厅还可以满足客人需要的定时服务，比如客人可能当前正在回家的路 上，要求 1 个小时后才开始炒他的菜，只要订单还在，厨师就不会忘记。客人也可以很方便地打 电话来撤销订单。另外如果有太多的客人点餐，厨房可以按照订单的顺序排队炒菜。 这些记录着订餐信息的清单，便是命令模式中的命令对象。</p>
<p>命令模式是最简单和优雅的模式之一，命令模式中的命令（command）指的是一个执行某些 特定事情的指令。 命令模式最常见的应用场景是：有时候需要向某些对象发送请求，但是并不知道请求的接收 者是谁，也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序，使得请求发送者和请求接收者能够消除彼此之间的耦合关系。</p>
<p>命令模 式将过程式的请求调用封装在 command 对象的 execute 方法里，通过封装方法调用，我们可以把 运算块包装成形。command 对象可以被四处传递，所以在调用命令的时候，客户（Client）不需要 关心事情是如何进行的。 </p>
<p>命令模式的由来，其实是回调（callback）函数的一个面向对象的替代品。</p>
<p>JavaScript 作为将函数作为一等对象的语言，跟策略模式一样，命令模式也早已融入到了 JavaScript 语言之中。运算块不一定要封装在 command.execute 方法中，也可以封装在普通函数中。 函数作为一等对象，本身就可以被四处传递。即使我们依然需要请求“接收者”，那也未必使用 面向对象的方式，闭包可以完成同样的功能。 </p>
<p>在面向对象设计中，命令模式的接收者被当成 command 对象的属性保存起来，同时约定执行 命令的操作调用 command.execute 方法。在使用闭包的命令模式实现中，接收者被封闭在闭包产 生的环境中，执行命令的操作可以更加简单，仅仅执行回调函数即可。无论接收者被保存为对象 的属性，还是被封闭在闭包产生的环境中，在将来执行命令的时候，接收者都能被顺利访问。用 闭包实现的命令模式如下代码所示：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> setCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> button, func </span>)</span>&#123; </span><br><span class="line">    button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        func(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> MenuBar = &#123; </span><br><span class="line">    refresh: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="built_in">console</span>.log( <span class="string">'刷新菜单界面'</span> ); </span><br><span class="line">    &#125;</span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> RefreshMenuBarCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> receiver </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        execute: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">            receiver.refresh();</span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> setCommand = <span class="function"><span class="keyword">function</span>(<span class="params"> button, command </span>)</span>&#123; </span><br><span class="line">    button.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        command.execute(); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar ); </span><br><span class="line">setCommand( button1, refreshMenuBarCommand );</span><br></pre></td></tr></table></figure>

<h2 id="撤销与重做"><a href="#撤销与重做" class="headerlink" title="撤销与重做"></a>撤销与重做</h2><p>节我们讨论了如何撤销一个命令。很多时候，我们需要撤销一系列的命令。比如在一个 围棋程序中，现在已经下了 10 步棋，我们需要一次性悔棋到第 5 步。在这之前，我们可以把所 有执行过的下棋命令都储存在一个历史列表中，然后倒序循环来依次执行这些命令的 undo 操作， 直到循环执行到第 5 个命令为止。 </p>
<p>然而，在某些情况下无法顺利地利用 undo 操作让对象回到 execute 之前的状态。比如在一个 Canvas 画图的程序中，画布上有一些点，我们在这些点之间画了 N 条曲线把这些点相互连接起 来，当然这是用命令模式来实现的。但是我们却很难为这里的命令对象定义一个擦除某条曲线的 undo 操作，因为在 Canvas 画图中，擦除一条线相对不容易实现。</p>
<p>这时候最好的办法是先清除画布，然后把刚才执行过的命令全部重新执行一遍，这一点同样 可以利用一个历史列表堆栈办到。记录命令日志，然后重复执行它们，这是逆转不可逆命令的一 个好办法</p>
<h2 id="命令队列"><a href="#命令队列" class="headerlink" title="命令队列"></a>命令队列</h2><p>队列在动画中的运用场景也非常多，比如之前的小球运动程序有可能遇到另外一个问题：有 些用户反馈，这个程序只适合于 APM 小于 20 的人群，大部分用户都有快速连续点击按钮的习惯， 当用户第二次点击 button 的时候，此时小球的前一个动画可能尚未结束，于是前一个动画会骤然 停止，小球转而开始第二个动画的运动过程。但这并不是用户的期望，用户希望这两个动画会排 队进行。 </p>
<p>把请求封装成命令对象的优点在这里再次体现了出来，对象的生命周期几乎是永久的，除非 我们主动去回收它。也就是说，命令对象的生命周期跟初始请求发生的时间无关，command 对象 的 execute 方法可以在程序运行的任何时刻执行，即使点击按钮的请求早已发生，但我们的命令 对象仍然是有生命的。 所以我们可以把 div 的这些运动过程都封装成命令对象，再把它们压进一个队列堆栈，当动 画执行完，也就是当前 command 对象的职责完成之后，会主动通知队列，此时取出正在队列中等 待的第一个命令对象，并且执行它。</p>
<p>我们比较关注的问题是，一个动画结束后该如何通知队列。通常可以使用回调函数来通知队 列，除了回调函数之外，还可以选择发布订阅模式。即在一个动画结束后发布一个消息，订阅 者接收到这个消息之后，便开始执行队列里的下一个动画。读者可以尝试按照这个思路来自行实 现一个队列动画</p>
<p>宏命令是一组命令的集合，通过执行宏命令的方式，可以一次执行一批命令。想象一下，家 里有一个万能遥控器，每天回家的时候，只要按一个特别的按钮，它就会帮我们关上房间门，顺 便打开电脑并登录 QQ</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本章我们学习了命令模式。跟许多其他语言不同，JavaScript 可以用高阶函数非常方便地实 现命令模式。命令模式在 JavaScript 语言中是一种隐形的模式</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%80%EF%BC%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/28/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/JavaScript%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%B8%80%EF%BC%89/" class="post-title-link" itemprop="url">JavaScript的设计模式（一）</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-28 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-28T12:18:39+08:00">2021-10-28</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 15:40:52" itemprop="dateModified" datetime="2021-11-03T15:40:52+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/" itemprop="url" rel="index"><span itemprop="name">设计模式</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>本博客参考曾探所著&lt;&lt;JavaScript设计模式与开发实践&gt;&gt;，仅为个人学习总结，侵删。</p>
<h2 id="多态"><a href="#多态" class="headerlink" title="多态"></a>多态</h2><p>多态是面向对象编程的一个重要概念，在Java中，通常使用向上转型来实现多态，让两者都继承于一个统一的抽象类，之后再分别对抽象类中的方法进行不同的实现，这样就实现了多态。</p>
<p>而JavaScript的变量类型在运行期是可变的，一个JavaScript对象即可以表示Duck类型对象，又可以便是Chicken类型对象，其多态性是与生俱来的。由此，对于JavaScript来说，某一种动物能否发出叫声，只取决于它有没有makeSound方法，而不取决于它是否是某种类型的对象。</p>
<p>多态的根本在于：你不必再向对象询问你是什么类型，而后根据答案调用对象的行为，你只用调用行为，其他的多态机制会安排妥当。其最根本作用：把过程化的条件分支语句转化为对象的多态性，从而消除这些条件分支语句。</p>
<p>多态是设计模式中的重中之重，很多设计模式的实现都离不开多态：</p>
<p>拿命令模式来说，请求被封装在一些命令对象中，这使得命令的调用者和命令的接收者可 以完全解耦开来，当调用命令的 execute 方法时，不同的命令会做不同的事情，从而会产生不同 的执行结果。而做这些事情的过程是早已被封装在命令对象内部的，作为调用命令的客户，根本 不必去关心命令执行的具体过程。  </p>
<p>在组合模式中，多态性使得客户可以完全忽略组合对象和叶节点对象之前的区别，这正是 组合模式最大的作用所在。对组合对象和叶节点对象发出同一个消息的时候，它们会各自做自己 应该做的事情，组合对象把消息继续转发给下面的叶节点对象，叶节点对象则会对这些消息作出 真实的反馈。</p>
<p> 在策略模式中，Context 并没有执行算法的能力，而是把这个职责委托给了某个策略对象。 每个策略对象负责的算法已被各自封装在对象内部。当我们对这些策略对象发出“计算”的消息 时，它们会返回各自不同的计算结果。</p>
<h2 id="封装"><a href="#封装" class="headerlink" title="封装"></a>封装</h2><p>在许多其他语言的对象系统中，封装数据是由语法解析来实现的，这些语言提供了private、public、protected等关键字来提供不同的访问权限；但JS没有提供这些支持，只能用变量作用域实现封装特性。1、使用let来生成块级作用域；2、使用函数作用域的闭包性质；3、通过Symbol来创建私有属性。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> myObject = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> __name = <span class="string">'sven'</span>; <span class="comment">// 私有（private）变量</span></span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        getName: <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; <span class="comment">// 公开（public）方法</span></span><br><span class="line">            <span class="keyword">return</span> __name; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line"><span class="built_in">console</span>.log( myObject.getName() ); <span class="comment">// 输出：sven </span></span><br><span class="line"><span class="built_in">console</span>.log( myObject.__name ) <span class="comment">// 输出：undefined</span></span><br></pre></td></tr></table></figure>

<p>上面的封装其实是数据层面的封装，但这只是一种狭义的定义，封装还应该包括：隐藏实现细节、设计细节、隐藏对象的类型。封装使对象之间的耦合变松散，对象之间通过暴露的API接口来通信。当我们修改一个对象时，可以随意地修改它的内部实现，只要对外的接口没有变化，就不会影响到程序的其他功能。</p>
<p>封装实现细节的例子非常之多。拿迭代器来说明，迭代器的作用是在不暴露一个聚合对象的 内部表示的前提下，提供一种方式来顺序访问这个聚合对象。我们编写了一个 each 函数，它的 作用就是遍历一个聚合对象，使用这个 each 函数的人不用关心它的内部是怎样实现的，只要它 提供的功能正确便可以。即使 each 函数修改了内部源代码，只要对外的接口或者调用方式没有 变化，用户就不用关心它内部实现的改变</p>
<p>封装类型是静态类型语言中一种重要的封装方式。一般而言，封装类型是通过抽象类和接口 来进行的。把对象的真正类型隐藏在抽象类或者接口之后，相比对象的类型，客户更关心对象 的行为。在许多静态语言的设计模式中，想方设法地去隐藏对象的类型，也是促使这些模式诞生 的原因之一。比如工厂方法模式、组合模式等。  当然在 JavaScript 中，并没有对抽象类和接口的支持。JavaScript 本身也是一门类型模糊的语 言。在封装类型方面，JavaScript 没有能力，也没有必要做得更多。对于 JavaScript 的设计模式实 现来说，不区分类型是一种失色，也可以说是一种解脱。</p>
<p>拿创建型模式来说，要创建一个对象，是一种抽象行为，而具体创建什么对象则是可以变化 的，创建型模式的目的就是封装创建对象的变化。而结构型模式封装的是对象之间的组合关系。 行为型模式封装的是对象的行为变化。  通过封装变化的方式，把系统中稳定不变的部分和容易变化的部分隔离开来，在系统的演变 过程中，我们只需要替换那些容易变化的部分，如果这些部分是已经封装好的，替换起来也相对容易。这可以最大程度地保证程序的稳定性和可扩展性。</p>
<h2 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h2><p>JS使用的是基于原型链的继承形式。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*11、继承的实现方式与比较</span></span><br><span class="line"><span class="comment">(1)、借助call来实现继承(构造函数继承)</span></span><br><span class="line"><span class="comment">这样写的时候子类虽然能够拿到父类的属性值，但是问题是父类原型对象中一旦存在方法那么子类无法继承。</span></span><br><span class="line"><span class="comment">通过使用call()或apply()方法，Parent构造函数在为Child的实例创建的新对象的上下文执行了，</span></span><br><span class="line"><span class="comment">就相当于新的Child实例对象上运行了Parent()函数中的所有初始化代码，结果就是每个实例都有自己的info属性。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent1</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent1'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">Child1</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">    Parent1.call(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child1'</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="keyword">new</span> Child1);</span><br><span class="line"><span class="comment">/*(2)、原型链实现继承</span></span><br><span class="line"><span class="comment">将父类的实例作为子类的原型：父类的所有引用属性（info）会被所有子类共享，更改一个子类的引用属性，其他子类也会受影响</span></span><br><span class="line"><span class="comment">如下：两个child实例用的是一个原型对象，修改其中之一的play，会影响另一个的。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent2</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent2'</span>;</span><br><span class="line">    <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">Child2</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child2'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">Child2.prototype = <span class="keyword">new</span> Parent2();</span><br><span class="line"><span class="built_in">console</span>.log(<span class="keyword">new</span> Child2());</span><br><span class="line"><span class="comment">/*(3)、组合继承：同时应用上面两种继承方法</span></span><br><span class="line"><span class="comment">使用原型链继承原型上的引用属性和方法，而通过构造函数继承实例属性，这样既可以把方法定义在原型上以实现重用，又可以让每个实例都有自己的属性 */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent3</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent3'</span>;</span><br><span class="line">    <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">Child3</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    Parent3.call(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child3'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  Child3.prototype = <span class="keyword">new</span> Parent3();</span><br><span class="line">  <span class="keyword">var</span> s3 = <span class="keyword">new</span> Child3();</span><br><span class="line">  <span class="keyword">var</span> s4 = <span class="keyword">new</span> Child3();</span><br><span class="line">  s3.play.push(<span class="number">4</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(s3.play, s4.play);</span><br><span class="line"></span><br><span class="line"><span class="comment">/*(4)、寄生组合继承</span></span><br><span class="line"><span class="comment">上面的组合继承有2点需要简单优化，1、避免parent构造函数执行2次；2、子类的构造函数应该指向Child，而不是Parent */</span></span><br><span class="line"><span class="comment">//new创建一个对象，执行构造函数。Object.create相当于创建一个对象，但是不执行构造函数。</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Parent5</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.name = <span class="string">'parent5'</span>;</span><br><span class="line">    <span class="keyword">this</span>.play = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">Child5</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    Parent5.call(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">this</span>.type = <span class="string">'child5'</span>;</span><br><span class="line">  &#125;</span><br><span class="line">Child5.prototype = <span class="built_in">Object</span>.create(Parent5.prototype);</span><br><span class="line">Child5.prototype.constructor = Child5;</span><br></pre></td></tr></table></figure>

<h2 id="AOP"><a href="#AOP" class="headerlink" title="AOP"></a>AOP</h2><p>AOP：面向切面编程的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来，这些功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来后，再通过“动态织入”的方式掺入业务模块中。这样做的好处是：首先可以保持业务逻辑模块的纯净与高内聚特性，其次是可以方便地复用日志统计等功能模块。</p>
<p>在Java中，可以通过反射、动态代理的机制来实现AOP。而在JavaScript中，AOP的实现更简单，指把一个函数动态织入到另外一个函数之中，通过扩展Function.prototype来实现。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.before = <span class="function"><span class="keyword">function</span>(<span class="params">beforefn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> _self = <span class="keyword">this</span>; <span class="comment">//保存原函数的引用</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123; <span class="comment">//返回包含原函数和新函数的代理函数</span></span><br><span class="line">        beforefn.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);<span class="comment">//执行原函数，修正this</span></span><br><span class="line">        <span class="keyword">return</span> _self.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);<span class="comment">//执行原函数</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="built_in">Function</span>.prototype.after = <span class="function"><span class="keyword">function</span>(<span class="params">afterfn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> _self = <span class="keyword">this</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> ret = _self.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);<span class="comment">//先执行原函数,再执行新定义的函数</span></span><br><span class="line">        afterfn.apply(<span class="keyword">this</span>, <span class="built_in">arguments</span>);</span><br><span class="line">        <span class="keyword">return</span> ret;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> func = fucntion()&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">2</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">func = func.before(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">1</span>);</span><br><span class="line">&#125;).after(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="number">3</span>);</span><br><span class="line">&#125;)</span><br><span class="line">func();</span><br><span class="line"><span class="comment">//执行该函数，会打印1 2 3。</span></span><br></pre></td></tr></table></figure>

<p>这种使用AOP的方式给函数添加职责，是JS中一种巧妙的装饰器模式实现。</p>
<h2 id="高阶函数"><a href="#高阶函数" class="headerlink" title="高阶函数"></a>高阶函数</h2><h3 id="currying"><a href="#currying" class="headerlink" title="currying"></a>currying</h3><p>柯里化又称部分求职，一个currying的函数首先会接收一些参数，但并不会立即求职，而是继续返回另外一个函数，刚才传入的参数再函数形成的闭包中被保存起来。待到真正需要求值得时候，之前传入得所有参数都会一次性地用于求值。</p>
<p>通用的function currying(){}，其接受一个参数，即要被currying的函数。在该例子中，这个函数的作用是遍历本月每天的开销并求出它们的总和。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> currying = <span class="function"><span class="keyword">function</span>(<span class="params">fn</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> args = [];</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">arguments</span>.length === <span class="number">0</span>) &#123;</span><br><span class="line">            <span class="keyword">return</span> fn.apply(<span class="keyword">this</span>, args);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            [].push.apply(args, <span class="built_in">arguments</span>);</span><br><span class="line">            <span class="keyword">return</span> <span class="built_in">arguments</span>.callee;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="uncurrying"><a href="#uncurrying" class="headerlink" title="uncurrying"></a>uncurrying</h3><p>当我们调用对象的某个方法时，其实不用去关心该对象原本是否被设计为拥有该方法，我们通常可以使用call、apply来借用原本不属于它的方法。我们常常让类数组去借用Array.prototype的方法。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line"> <span class="built_in">Array</span>.prototype.push.call( <span class="built_in">arguments</span>, <span class="number">4</span> ); <span class="comment">// arguments 借用 Array.prototype.push 方法</span></span><br><span class="line"> <span class="built_in">console</span>.log( <span class="built_in">arguments</span> ); <span class="comment">// 输出：[1, 2, 3, 4] </span></span><br><span class="line">&#125;)( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> );</span><br></pre></td></tr></table></figure>

<p>那么如何把泛化this的过程提取出来？</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Function</span>.prototype.uncurrying = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123; </span><br><span class="line">    <span class="keyword">var</span> self = <span class="keyword">this</span>; <span class="comment">//self此时是Array.prototype.push</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123; </span><br><span class="line">        <span class="keyword">var</span> obj = <span class="built_in">Array</span>.prototype.shift.call( <span class="built_in">arguments</span> );<span class="comment">//arguments对象的第一个元素被截去</span></span><br><span class="line">        <span class="keyword">return</span> self.apply( obj, <span class="built_in">arguments</span> ); </span><br><span class="line">    &#125;; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">在类数组对象 arguments 借用 Array.prototype 的方法之前，</span></span><br><span class="line"><span class="comment">先把 Array.prototype.push.call这句代码转换为一个通用的 push 函数：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> push = <span class="built_in">Array</span>.prototype.push.uncurrying(); </span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    push( <span class="built_in">arguments</span>, <span class="number">4</span> ); </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="built_in">arguments</span> ); <span class="comment">// 输出：[1, 2, 3, 4] </span></span><br><span class="line">&#125;)( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span> ); </span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">通过 uncurrying 的方式，Array.prototype.push.call 变成了一个通用的 push 函数。这样一来，</span></span><br><span class="line"><span class="comment">push 函数的作用就跟 Array.prototype.push 一样了，同样不仅仅局限于只能操作 array 对象。而</span></span><br><span class="line"><span class="comment">对于使用者而言，调用 push 函数的方式也显得更加简洁和意图明了。</span></span><br><span class="line"><span class="comment">我们还可以一次性地把 Array.prototype 上的方法“复制”到 array 对象上，同样这些方法可</span></span><br><span class="line"><span class="comment">操作的对象也不仅仅只是 array 对象：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, fn, ary = [ <span class="string">'push'</span>, <span class="string">'shift'</span>, <span class="string">'forEach'</span> ]; fn = ary[ i++ ]; )&#123; </span><br><span class="line">    <span class="built_in">Array</span>[ fn ] = <span class="built_in">Array</span>.prototype[ fn ].uncurrying(); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> obj = &#123; </span><br><span class="line">    <span class="string">"length"</span>: <span class="number">3</span>, </span><br><span class="line">    <span class="string">"0"</span>: <span class="number">1</span>, </span><br><span class="line">    <span class="string">"1"</span>: <span class="number">2</span>, </span><br><span class="line">    <span class="string">"2"</span>: <span class="number">3</span> </span><br><span class="line">&#125;; </span><br><span class="line"><span class="built_in">Array</span>.push( obj, <span class="number">4</span> ); <span class="comment">// 向对象中添加一个元素</span></span><br><span class="line"><span class="built_in">console</span>.log( obj.length ); <span class="comment">// 输出：4 </span></span><br><span class="line"><span class="keyword">var</span> first = <span class="built_in">Array</span>.shift( obj ); <span class="comment">// 截取第一个元素</span></span><br><span class="line"><span class="built_in">console</span>.log( first ); <span class="comment">// 输出：1 </span></span><br><span class="line"><span class="built_in">console</span>.log( obj ); <span class="comment">// 输出：&#123;0: 2, 1: 3, 2: 4, length: 3&#125; </span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Array</span>.forEach( obj, <span class="function"><span class="keyword">function</span>(<span class="params"> i, n </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( n ); <span class="comment">// 分别输出：0, 1, 2 </span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h3 id="分时函数"><a href="#分时函数" class="headerlink" title="分时函数"></a>分时函数</h3><p>在短时间内往页面中大量添加DOM节点显然会让浏览器吃不消，往往会导致浏览器的卡顿或者假死。这个问题的解决方法之一是下面的timeChunk函数，timeChunk函数让创建节点的工作分批进行，比如：1秒钟创建1000个节点，改为每隔200ms创建8个节点。该函数接收3个参数，第1个参数是创建节点时需要用到的数据，第2个参数是封装了创建节点逻辑的函数，第3个参数表示每一批创建的节点数量。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> timeChunk = <span class="function"><span class="keyword">function</span> (<span class="params">ary, fn, count</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> obj;<span class="keyword">var</span> t;</span><br><span class="line">    <span class="keyword">var</span> len = ary.length;</span><br><span class="line">    <span class="keyword">var</span> start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>; i &lt; <span class="built_in">Math</span>.min(count || <span class="number">1</span>, ary.length); i++) &#123;</span><br><span class="line">            <span class="keyword">var</span> obj = ary.shift();</span><br><span class="line">            fn(obj);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        t = setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            <span class="keyword">if</span> (ary.length === <span class="number">0</span>) &#123;<span class="comment">//如果全部节点都已经被创建好</span></span><br><span class="line">                <span class="keyword">return</span> clearInterval(t);</span><br><span class="line">            &#125;</span><br><span class="line">            start();</span><br><span class="line">        &#125;, <span class="number">200</span>); <span class="comment">//分批执行的时间间隔，也可以用参数的形式传入</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="单例模式"><a href="#单例模式" class="headerlink" title="单例模式"></a>单例模式</h1><p>要实现一个标准的单例模式并不复杂，无非是用一个变量来标志当前是否已经为某个类创建 过对象，如果是，则在下一次获取该类的实例时，直接返回之前创建的对象。我们通过 Singleton.getInstance 来获取 Singleton 类的唯一对象，这种方式相对简单，但有 一个问题，就是增加了这个类的“不透明性”，Singleton 类的使用者必须知道这是一个单例类， 跟以往通过 new XXX 的方式来获取对象不同，这里偏要使用 Singleton.getInstance 来获取对象。</p>
<h2 id="透明单例"><a href="#透明单例" class="headerlink" title="透明单例"></a>透明单例</h2><p>我们现在的目标是实现一个“透明”的单例类，用户从这个类中创建对象的时候，可以像使 用其他任何普通类一样。在下面的例子中，我们将使用 CreateDiv 单例类，它的作用是负责在页 面中创建唯一的 div 节点，代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> CreateDiv = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> instance; </span><br><span class="line">    <span class="keyword">var</span> CreateDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( instance )&#123; </span><br><span class="line">            <span class="keyword">return</span> instance; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">this</span>.html = html; </span><br><span class="line">        <span class="keyword">this</span>.init(); </span><br><span class="line">        <span class="keyword">return</span> instance = <span class="keyword">this</span>; </span><br><span class="line">    &#125;; </span><br><span class="line">    CreateDiv.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); </span><br><span class="line">        div.innerHTML = <span class="keyword">this</span>.html; </span><br><span class="line">        <span class="built_in">document</span>.body.appendChild( div ); </span><br><span class="line">    &#125;; </span><br><span class="line">    <span class="keyword">return</span> CreateDiv; </span><br><span class="line">&#125;)(); </span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> CreateDiv( <span class="string">'sven1'</span> ); </span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> CreateDiv( <span class="string">'sven2'</span> ); </span><br><span class="line">alert ( a === b ); <span class="comment">// true</span></span><br></pre></td></tr></table></figure>

<p>虽然现在完成了一个透明的单例类的编写，但它同样有一些缺点。 为了把 instance 封装起来，我们使用了自执行的匿名函数和闭包，并且让这个匿名函数返回 真正的 Singleton 构造方法，这增加了一些程序的复杂度，阅读起来也不是很舒服</p>
<p>CreateDiv 的构造函数实际上负责了两件事情。第一是创建对象和执行初始化init 方法，第二是保证只有一个对象。虽然我们目前还没有接触过“单一职责原则”的概念， 但可以明确的是，这是一种不好的做法，至少这个构造函数看起来很奇怪。</p>
<h2 id="代理实现"><a href="#代理实现" class="headerlink" title="代理实现"></a>代理实现</h2><p>现在我们通过引入代理类的方式，来解决上面提到的问题。 我们依然使用上面的代码，首先在 CreateDiv 构造函数中，把负责管理单例的代码移除 出去，使它成为一个普通的创建 div 的类。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> CreateDiv = <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.html = html;</span><br><span class="line">    <span class="keyword">this</span>.init(); </span><br><span class="line">&#125;; </span><br><span class="line">CreateDiv.prototype.init = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); </span><br><span class="line">    div.innerHTML = <span class="keyword">this</span>.html; </span><br><span class="line">    <span class="built_in">document</span>.body.appendChild( div ); </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">//接下来引入代理类 proxySingletonCreateDiv</span></span><br><span class="line"><span class="keyword">var</span> ProxySingletonCreateDiv = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> instance; </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> html </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !instance )&#123; </span><br><span class="line">            instance = <span class="keyword">new</span> CreateDiv( html ); </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> instance; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;)(); </span><br><span class="line"><span class="keyword">var</span> a = <span class="keyword">new</span> ProxySingletonCreateDiv( <span class="string">'sven1'</span> ); </span><br><span class="line"><span class="keyword">var</span> b = <span class="keyword">new</span> ProxySingletonCreateDiv( <span class="string">'sven2'</span> ); </span><br><span class="line">alert ( a === b );</span><br></pre></td></tr></table></figure>

<p>通过引入代理类的方式，我们同样完成了一个单例模式的编写，跟之前不同的是，现在我们 把负责管理单例的逻辑移到了代理类 proxySingletonCreateDiv 中。这样一来，CreateDiv 就变成了 一个普通的类，它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果。 本例是缓存代理的应用之一。</p>
<h2 id="惰性单例"><a href="#惰性单例" class="headerlink" title="惰性单例"></a>惰性单例</h2><p>惰性单例指的是在需要的时候才创建对象实例。惰性单例是单例模式的重点，这种技术在实 际开发中非常有用，有用的程度可能超出了我们的想象，实际上在本章开头就使用过这种技术， instance 实例对象总是在我们调用 Singleton.getInstance 的时候才被创建，而不是在页面加载好 的时候就创建。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> getSingle = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> result; </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> result || ( result = fn .apply(<span class="keyword">this</span>, <span class="built_in">arguments</span> ) ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> createLoginLayer = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> div = <span class="built_in">document</span>.createElement( <span class="string">'div'</span> ); </span><br><span class="line">    div.innerHTML = <span class="string">'我是登录浮窗'</span>; </span><br><span class="line">    div.style.display = <span class="string">'none'</span>; </span><br><span class="line">    <span class="built_in">document</span>.body.appendChild( div ); </span><br><span class="line">    <span class="keyword">return</span> div; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> createSingleLoginLayer = getSingle( createLoginLayer );</span><br><span class="line"><span class="built_in">document</span>.getElementById( <span class="string">'loginBtn'</span> ).onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> loginLayer = createSingleLoginLayer(); </span><br><span class="line">    loginLayer.style.display = <span class="string">'block'</span>; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>在这个例子中，我们把创建实例对象的职责和管理单例的职责分别放置在两个方法里，这两 个方法可以独立变化而互不影响，当它们连接在一起的时候，就完成了创建唯一实例对象的功能， 看起来是一件挺奇妙的事情。</p>
<h1 id="策略模式"><a href="#策略模式" class="headerlink" title="策略模式"></a>策略模式</h1><p><strong>策略模式的定义是：定义一系列的算法，把它们一个个封装起来，并且使它们可以相互替换。</strong></p>
<p>策略模式指的是定义一系 列的算法，把它们一个个封装起来。将不变的部分和变化的部分隔开是每个设计模式的主题，策 略模式也不例外，策略模式的目的就是将算法的使用与算法的实现分离开来。</p>
<p>一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类，策略类封装了具体 的算法，并负责具体的计算过程。第二个部分是环境类 Context，Context 接受客户的请求，随后 把请求委托给某一个策略类。要做到这点，说明 Context 中要维持对某个策略对象的引用。</p>
<h2 id="简单实现"><a href="#简单实现" class="headerlink" title="简单实现"></a>简单实现</h2><p>第一个版本是模仿传统面向对象语言中的实现。我们先 把每种绩效的计算规则都封装在对应的策略类里面：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> performanceS = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">performanceS.prototype.calculate = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">4</span>; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> performanceA = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">performanceA.prototype.calculate = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">3</span>; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> performanceB = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;&#125;; </span><br><span class="line">performanceB.prototype.calculate = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">2</span>; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">//接下来定义奖金类 Bonus：</span></span><br><span class="line"><span class="keyword">var</span> Bonus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.salary = <span class="literal">null</span>; <span class="comment">// 原始工资</span></span><br><span class="line">    <span class="keyword">this</span>.strategy = <span class="literal">null</span>; <span class="comment">// 绩效等级对应的策略对象</span></span><br><span class="line">&#125;; </span><br><span class="line">Bonus.prototype.setSalary = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.salary = salary; <span class="comment">// 设置员工的原始工资</span></span><br><span class="line">&#125;; </span><br><span class="line">Bonus.prototype.setStrategy = <span class="function"><span class="keyword">function</span>(<span class="params"> strategy </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.strategy = strategy; <span class="comment">// 设置员工绩效等级对应的策略对象</span></span><br><span class="line">&#125;; </span><br><span class="line">Bonus.prototype.getBonus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; <span class="comment">// 取得奖金数额</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">this</span>.strategy.calculate( <span class="keyword">this</span>.salary ); <span class="comment">// 把计算奖金的操作委托给对应的策略对象</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">定义一系列的算法，把它们各自封装成策略类，算法被</span></span><br><span class="line"><span class="comment">封装在策略类内部的方法里。在客户对 Context 发起请求的时候，Context 总是把请求委托给这些</span></span><br><span class="line"><span class="comment">策略对象中间的某一个进行计算</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> bonus = <span class="keyword">new</span> Bonus(); </span><br><span class="line">bonus.setSalary( <span class="number">10000</span> ); </span><br><span class="line">bonus.setStrategy( <span class="keyword">new</span> performanceS() ); <span class="comment">// 设置策略对象</span></span><br><span class="line"><span class="built_in">console</span>.log( bonus.getBonus() ); <span class="comment">// 输出：40000 </span></span><br><span class="line">bonus.setStrategy( <span class="keyword">new</span> performanceA() ); <span class="comment">// 设置策略对象</span></span><br><span class="line"><span class="built_in">console</span>.log( bonus.getBonus() ); <span class="comment">// 输出：30000</span></span><br></pre></td></tr></table></figure>

<p>我们让 strategy 对象从各个策略类中创建而来，这是模拟一些传统面向对象语言的实现。实际上在 JavaScript 语言中，函数也是对象，所以更简单和直接的做法是把 strategy 直接定义为函数：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> strategies = &#123; </span><br><span class="line">    <span class="string">"S"</span>: <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> salary * <span class="number">4</span>; </span><br><span class="line">    &#125;, </span><br><span class="line">    <span class="string">"A"</span>: <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> salary * <span class="number">3</span>; </span><br><span class="line">    &#125;, </span><br><span class="line">    <span class="string">"B"</span>: <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">return</span> salary * <span class="number">2</span>; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//同样，Context 也没有必要必须用 Bonus 类来表示，我们依然用 calculateBonus 函数充当Context 来接受用户的请求。</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> calculateBonus = <span class="function"><span class="keyword">function</span>(<span class="params"> level, salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> strategies[ level ]( salary ); </span><br><span class="line">&#125;; </span><br><span class="line"><span class="built_in">console</span>.log(calculateBonus( <span class="string">'S'</span>, <span class="number">20000</span> ) ); <span class="comment">// 输出：80000 </span></span><br><span class="line"><span class="built_in">console</span>.log(calculateBonus( <span class="string">'A'</span>, <span class="number">10000</span> ) ); <span class="comment">// 输出：30000 </span></span><br><span class="line"><span class="comment">//在接下来的缓动动画和表单验证的例子中，我们用到的都是这种函数形式的策略对象。</span></span><br></pre></td></tr></table></figure>

<p>多态：通过使用策略模式重构代码，我们消除了原程序中大片的条件分支语句。所有跟计算奖金有 关的逻辑不再放在 Context 中，而是分布在各个策略对象中。Context 并没有计算奖金的能力，而 是把这个职责委托给了某个策略对象。每个策略对象负责的算法已被各自封装在对象内部。当我 们对这些策略对象发出“计算奖金”的请求时，它们会返回各自不同的计算结果，这正是对象多 态性的体现，也是“它们可以相互替换”的目的。</p>
<h2 id="表单验证"><a href="#表单验证" class="headerlink" title="表单验证"></a>表单验证</h2><p>下面我们将用策略模式来重构表单校验的代码，很显然第一步我们要把这些校验逻辑都封装成策略对象：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> strategies = &#123; </span><br><span class="line">    isNonEmpty: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>&#123; <span class="comment">// 不为空</span></span><br><span class="line">        <span class="keyword">if</span> ( value === <span class="string">''</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg ; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;, </span><br><span class="line">    minLength: <span class="function"><span class="keyword">function</span>(<span class="params"> value, length, errorMsg </span>)</span>&#123; <span class="comment">// 限制最小长度</span></span><br><span class="line">        <span class="keyword">if</span> ( value.length &lt; length )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg;</span><br><span class="line">        &#125; </span><br><span class="line">    &#125;, </span><br><span class="line">    isMobile: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>&#123; <span class="comment">// 手机号码格式</span></span><br><span class="line">        <span class="keyword">if</span> ( !<span class="regexp">/(^1[3|5|8][0-9]&#123;9&#125;$)/</span>.test( value ) )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">接下来我们准备实现 Validator类。Validator 类在这里作为 Context，负责接收用户的请求</span></span><br><span class="line"><span class="comment">并委托给 strategy 对象。在给出 Validator 类的代码之前，有必要提前了解用户是如何向 Validator</span></span><br><span class="line"><span class="comment">类发送请求的，这有助于我们知道如何去编写 Validator 类的代码。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> validataFunc = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> validator = <span class="keyword">new</span> Validator(); <span class="comment">// 创建一个 validator 对象</span></span><br><span class="line"> <span class="comment">/***************添加一些校验规则****************/</span> </span><br><span class="line">    validator.add( registerForm.userName, <span class="string">'isNonEmpty'</span>, <span class="string">'用户名不能为空'</span> ); </span><br><span class="line">    validator.add( registerForm.password, <span class="string">'minLength:6'</span>, <span class="string">'密码长度不能少于 6 位'</span> ); </span><br><span class="line">    validator.add( registerForm.phoneNumber, <span class="string">'isMobile'</span>, <span class="string">'手机号码格式不正确'</span> ); </span><br><span class="line">    <span class="keyword">var</span> errorMsg = validator.start(); <span class="comment">// 获得校验结果</span></span><br><span class="line">    <span class="keyword">return</span> errorMsg; <span class="comment">// 返回校验结果</span></span><br><span class="line">&#125; </span><br><span class="line"><span class="keyword">var</span> registerForm = <span class="built_in">document</span>.getElementById( <span class="string">'registerForm'</span> ); </span><br><span class="line">registerForm.onsubmit = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> errorMsg = validataFunc(); <span class="comment">// 如果 errorMsg 有确切的返回值，说明未通过校验</span></span><br><span class="line">    <span class="keyword">if</span> ( errorMsg )&#123; </span><br><span class="line">        alert ( errorMsg );</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; <span class="comment">// 阻止表单提交</span></span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//最后是Validator类的实现。</span></span><br><span class="line"><span class="keyword">var</span> Validator = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.cache = []; <span class="comment">// 保存校验规则</span></span><br><span class="line">&#125;; </span><br><span class="line">Validator.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> dom, rule, errorMsg </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> ary = rule.split( <span class="string">':'</span> ); <span class="comment">// 把 strategy 和参数分开，在有些策略里面":"后面是参数。</span></span><br><span class="line">    <span class="keyword">this</span>.cache.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; <span class="comment">// 把校验的步骤用空函数包装起来，并且放入 cache </span></span><br><span class="line">        <span class="keyword">var</span> strategy = ary.shift(); <span class="comment">// 用户挑选的 strategy </span></span><br><span class="line">        ary.unshift( dom.value ); <span class="comment">// 把 input 的 value 添加进参数列表</span></span><br><span class="line">        ary.push( errorMsg ); <span class="comment">// 把 errorMsg 添加进参数列表</span></span><br><span class="line">        <span class="keyword">return</span> strategies[ strategy ].apply( dom, ary ); </span><br><span class="line">    &#125;); </span><br><span class="line">&#125;;</span><br><span class="line">Validator.prototype.start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, validatorFunc; validatorFunc = <span class="keyword">this</span>.cache[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> msg = validatorFunc(); <span class="comment">// 开始校验，并取得校验后的返回信息</span></span><br><span class="line">        <span class="keyword">if</span> ( msg )&#123; <span class="comment">// 如果有确切的返回值，说明校验没有通过</span></span><br><span class="line">            <span class="keyword">return</span> msg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">/*使用策略模式重构代码后，我们仅仅通过“配置”的方式就可以完成一个表单的验证，</span></span><br><span class="line"><span class="comment">这些校验规则也可以复用在程序的任何地方，啊还能作为插件的形式，方便地被移植到其他项目中。</span></span><br><span class="line"><span class="comment">在修改某个校验规则时，只需要编写或改写少量的代码。</span></span><br><span class="line"><span class="comment">比如我们想将用户名输入框的校验规则改成用户名不能少于 4 个字符。可以看到，这时候的修改是毫不费力的。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">validator.add( registerForm.userName, <span class="string">'isNonEmpty'</span>, <span class="string">'用户名不能为空'</span> ); </span><br><span class="line"><span class="comment">// 改成：</span></span><br><span class="line">validator.add( registerForm.userName, <span class="string">'minLength:10'</span>, <span class="string">'用户名长度不能小于 10 位'</span> );</span><br></pre></td></tr></table></figure>

<p>为了专注于策略模式的使用，我们目前的验证实现留有一部分的缺陷：一个文本框只能对应一种校验规则，比如，想使用以下代码的形式来做校验：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line">validator.add( registerForm.userName, [&#123; </span><br><span class="line"> strategy: <span class="string">'isNonEmpty'</span>, </span><br><span class="line"> errorMsg: <span class="string">'用户名不能为空'</span> </span><br><span class="line">&#125;, &#123; </span><br><span class="line"> strategy: <span class="string">'minLength:6'</span>, </span><br><span class="line"> errorMsg: <span class="string">'用户名长度不能小于 10 位'</span> </span><br><span class="line">&#125;]); </span><br><span class="line"></span><br><span class="line"><span class="comment">//下面提供的代码可用于一个文本输入框对应多种校验规则：</span></span><br><span class="line"><span class="keyword">var</span> strategies = &#123; </span><br><span class="line">    isNonEmpty: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( value === <span class="string">''</span> )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;, </span><br><span class="line">    minLength: <span class="function"><span class="keyword">function</span>(<span class="params"> value, length, errorMsg </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( value.length &lt; length )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125;, </span><br><span class="line">    isMobile: <span class="function"><span class="keyword">function</span>(<span class="params"> value, errorMsg </span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( !<span class="regexp">/(^1[3|5|8][0-9]&#123;9&#125;$)/</span>.test( value ) )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"> <span class="comment">/***********************Validator 类**************************/</span> </span><br><span class="line"><span class="keyword">var</span> Validator = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">this</span>.cache = []; </span><br><span class="line">&#125;; </span><br><span class="line">Validator.prototype.add = <span class="function"><span class="keyword">function</span>(<span class="params"> dom, rules </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> self = <span class="keyword">this</span>; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, rule; rule = rules[ i++ ]; ) &#123; </span><br><span class="line">        (<span class="function"><span class="keyword">function</span>(<span class="params"> rule </span>)</span>&#123; </span><br><span class="line">            <span class="keyword">var</span> strategyAry = rule.strategy.split( <span class="string">':'</span> ); </span><br><span class="line">            <span class="keyword">var</span> errorMsg = rule.errorMsg; </span><br><span class="line">            self.cache.push(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">                <span class="keyword">var</span> strategy = strategyAry.shift();</span><br><span class="line">                strategyAry.unshift( dom.value ); </span><br><span class="line">                strategyAry.push( errorMsg ); </span><br><span class="line">                <span class="keyword">return</span> strategies[ strategy ].apply( dom, strategyAry ); </span><br><span class="line">            &#125;); </span><br><span class="line">        &#125;)( rule ) </span><br><span class="line">    &#125; </span><br><span class="line"> &#125;; </span><br><span class="line"></span><br><span class="line">Validator.prototype.start = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, validatorFunc; validatorFunc = <span class="keyword">this</span>.cache[ i++ ]; )&#123; </span><br><span class="line">        <span class="keyword">var</span> errorMsg = validatorFunc(); </span><br><span class="line">        <span class="keyword">if</span> ( errorMsg )&#123; </span><br><span class="line">            <span class="keyword">return</span> errorMsg; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"></span><br><span class="line"><span class="comment">/***********************客户调用代码**************************/</span> </span><br><span class="line"><span class="keyword">var</span> registerForm = <span class="built_in">document</span>.getElementById( <span class="string">'registerForm'</span> ); </span><br><span class="line"><span class="keyword">var</span> validataFunc = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> validator = <span class="keyword">new</span> Validator(); </span><br><span class="line">    validator.add( registerForm.userName, [&#123; </span><br><span class="line">        strategy: <span class="string">'isNonEmpty'</span>, </span><br><span class="line">        errorMsg: <span class="string">'用户名不能为空'</span> </span><br><span class="line">    &#125;, &#123; </span><br><span class="line">        strategy: <span class="string">'minLength:6'</span>, </span><br><span class="line">        errorMsg: <span class="string">'用户名长度不能小于 10 位'</span> </span><br><span class="line">    &#125;]); </span><br><span class="line">    validator.add( registerForm.password, [&#123; </span><br><span class="line">        strategy: <span class="string">'minLength:6'</span>, </span><br><span class="line">        errorMsg: <span class="string">'密码长度不能小于 6 位'</span> </span><br><span class="line">    &#125;]); </span><br><span class="line">    validator.add( registerForm.phoneNumber, [&#123; </span><br><span class="line">        strategy: <span class="string">'isMobile'</span>, </span><br><span class="line">        errorMsg: <span class="string">'手机号码格式不正确'</span> </span><br><span class="line">    &#125;]); </span><br><span class="line">    <span class="keyword">var</span> errorMsg = validator.start(); </span><br><span class="line">    <span class="keyword">return</span> errorMsg; </span><br><span class="line">&#125; </span><br><span class="line">registerForm.onsubmit = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> errorMsg = validataFunc(); </span><br><span class="line">    <span class="keyword">if</span> ( errorMsg )&#123; </span><br><span class="line">        alert ( errorMsg ); </span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>; </span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>策略模式的优点：1、策略模式利用组合、委托和多态等技术和思想，可以有效地避免多重条件选择语句。 2、策略模式提供了对开放—封闭原则的完美支持，将算法封装在独立的 strategy 中，使得它们易于切换，易于理解，易于扩展。 3、 策略模式中的算法也可以复用在系统的其他地方，从而避免许多重复的复制粘贴工作。4、在策略模式中利用组合和委托来让 Context 拥有执行算法的能力，这也是继承的一种更轻便的替代方案。</p>
<h2 id="优缺点"><a href="#优缺点" class="headerlink" title="优缺点"></a>优缺点</h2><p>缺点：1、使用策略模式会在程序中增加许多策略类或者策略对象，但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。2、要使用策略模式，必须了解所有的 strategy，必须了解各个 strategy 之间的不同点， 这样才能选择一个合适的 strategy。</p>
<p>“在函数作为一等对象的语言中，策略模式是隐形的。 strategy 就是值为函数的变量。”在 JavaScript 中，除了使用类来封装算法和行为之外，使用函数 当然也是一种选择。这些“算法”可以被封装到函数中并且四处传递，也就是我们常说的“高阶 函数”。实际上在 JavaScript 这种将函数作为一等对象的语言里，策略模式已经融入到了语言本身 当中，我们经常用高阶函数来封装不同的行为，并且把它传递到另一个函数中。当我们对这些函 数发出“调用”的消息时，不同的函数会返回不同的执行结果。</p>
<p>在 JavaScript 语言的策略模式中，策略类往往被函数所代替，这时策略模式就 成为一种“隐形”的模式。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> S = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">4</span>; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> A = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">3</span>; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> B = <span class="function"><span class="keyword">function</span>(<span class="params"> salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> salary * <span class="number">2</span>; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> calculateBonus = <span class="function"><span class="keyword">function</span>(<span class="params"> func, salary </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">return</span> func( salary ); </span><br><span class="line">&#125;; </span><br><span class="line">calculateBonus( S, <span class="number">10000</span> ); <span class="comment">// 输出：40000</span></span><br></pre></td></tr></table></figure>

<h1 id="代理模式"><a href="#代理模式" class="headerlink" title="代理模式"></a>代理模式</h1><p>代理模式是为一个对象提供一个代用品或占位符，以便控制对它的访问。代理模式的关键是，当客户不方便直接访问一个对象或者不满足需要的时候，提供一个替身 对象来控制对这个对象的访问，客户实际上访问的是替身对象。替身对象对请求做出一些处理之 后，再把请求转交给本体对象。</p>
<h2 id="代理分类"><a href="#代理分类" class="headerlink" title="代理分类"></a>代理分类</h2><p>代理 B 可以帮助 A 过滤掉一些请求，比如送花的人中年龄太大的或者没有宝马的，这种请求就可以直接在代理 B 处被拒绝掉。这种代理叫作<strong>保护代理</strong>。A 和 B 一个充当白脸，一个充当黑脸。白脸 A 继续保持 良好的女神形象，不希望直接拒绝任何人，于是找了黑脸 B 来控制对 A 的访问。 另外，假设现实中的花价格不菲，导致在程序世界里，new Flower 也是一个代价昂贵的操作， 那么我们可以把 new Flower 的操作交给代理 B 去执行，代理 B 会选择在 A 心情好时再执行 new  Flower，这是代理模式的另一种形式，叫作<strong>虚拟代理</strong>。</p>
<p>保护代理用于控制不同权限的对象对目标对象的访问，但在 JavaScript 并不容易实现保护代 理，因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式。</p>
<h3 id="图片预加载"><a href="#图片预加载" class="headerlink" title="图片预加载"></a>图片预加载</h3><p>在 Web 开发中，图片预加载是一种常用的技术，如果直接给某个 img 标签节点设置 src 属性， 由于图片过大或者网络不佳，图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading 图片占位，然后用异步的方式加载图片，等图片加载好了再把它填充到 img 节点里，这种 场景就很适合使用虚拟代理。 </p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*现在开始引入代理对象 proxyImage，通过这个代理对象，在图片被真正加载好之前，页面中</span></span><br><span class="line"><span class="comment">将出现一张占位的菊花图 loading.gif, 来提示用户图片正在加载。代码如下：*/</span></span><br><span class="line"><span class="keyword">var</span> myImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> imgNode = <span class="built_in">document</span>.createElement( <span class="string">'img'</span> ); </span><br><span class="line">    <span class="built_in">document</span>.body.appendChild( imgNode ); </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>&#123; </span><br><span class="line">            imgNode.src = src; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> proxyImage = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> img = <span class="keyword">new</span> Image; </span><br><span class="line">    img.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        myImage.setSrc( <span class="keyword">this</span>.src ); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> &#123; </span><br><span class="line">        setSrc: <span class="function"><span class="keyword">function</span>(<span class="params"> src </span>)</span>&#123; </span><br><span class="line">            myImage.setSrc( <span class="string">'file:// /C:/Users/svenzeng/Desktop/loading.gif'</span> ); </span><br><span class="line">            img.src = src; </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line">proxyImage.setSrc( <span class="string">'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg'</span> );</span><br></pre></td></tr></table></figure>

<h3 id="合并HTTP"><a href="#合并HTTP" class="headerlink" title="合并HTTP"></a>合并HTTP</h3><p>在 Web 开发中，也许最大的开销就是网络请求。 假设我们在做一个文件同步的功能，当我们选中一个 checkbox 的时候，它对应的文件就会被同 步到另外一台备用服务器上面，</p>
<p>解决方案是，我们可以通过一个代理函数 proxySynchronousFile 来收集一段时间之内的请求， 最后一次性发送给服务器。比如我们等待 2 秒之后才把这 2 秒之内需要同步的文件 ID 打包发给 服务器，如果不是对实时性要求非常高的系统，2 秒的延迟不会带来太大副作用，却能大大减轻 服务器的压力。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> synchronousFile = <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'开始同步文件，id 为: '</span> + id ); </span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">var</span> proxySynchronousFile = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> cache = [], <span class="comment">// 保存一段时间内需要同步的 ID </span></span><br><span class="line">        timer; <span class="comment">// 定时器</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"> id </span>)</span>&#123; </span><br><span class="line">        cache.push( id ); </span><br><span class="line">        <span class="keyword">if</span> ( timer )&#123; <span class="comment">// 保证不会覆盖已经启动的定时器</span></span><br><span class="line">            <span class="keyword">return</span>; </span><br><span class="line">        &#125; </span><br><span class="line">        timer = setTimeout(<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span><br><span class="line">            synchronousFile( cache.join( <span class="string">','</span> ) ); <span class="comment">// 2 秒后向本体发送需要同步的 ID 集合</span></span><br><span class="line">            clearTimeout( timer ); <span class="comment">// 清空定时器</span></span><br><span class="line">            timer = <span class="literal">null</span>; </span><br><span class="line">            cache.length = <span class="number">0</span>; <span class="comment">// 清空 ID 集合</span></span><br><span class="line">        &#125;, <span class="number">2000</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line"><span class="keyword">var</span> checkbox = <span class="built_in">document</span>.getElementsByTagName( <span class="string">'input'</span> ); </span><br><span class="line"><span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, c; c = checkbox[ i++ ]; )&#123;</span><br><span class="line">    c.onclick = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">if</span> ( <span class="keyword">this</span>.checked === <span class="literal">true</span> )&#123; </span><br><span class="line">            proxySynchronousFile( <span class="keyword">this</span>.id ); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h2 id="代理意义"><a href="#代理意义" class="headerlink" title="代理意义"></a>代理意义</h2><p>为了说明代理的意义，下面我们引入一个面向对象设计的原则——单一职责原则。 单一职责原则指的是，就一个类（通常也包括对象和函数等）而言，应该仅有一个引起它变 化的原因。如果一个对象承担了多项职责，就意味着这个对象将变得巨大，引起它变化的原因可 能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中，如果一个对象承担的职责过多， 等于把这些职责耦合到了一起，这种耦合会导致脆弱和低内聚的设计。当变化发生时，设计可能 会遭到意外的破坏。</p>
<p>纵观整个程序，我们并没有改变或者增加 MyImage 的接口，但是通过代理对象，实际上给系 统添加了新的行为。这是符合开放—封闭原则的。给 img 节点设置 src 和图片预加载这两个功能， 被隔离在两个对象里，它们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载， 那么只需要改成请求本体而不是请求代理对象即可。</p>
<h3 id="一致性"><a href="#一致性" class="headerlink" title="一致性"></a>一致性</h3><p>代理对象和本体都对外提供了 setSrc 方法，在客户看来，代理对象和本体 是一致的， 代理接手请求的过程对于用户来说是透明的，用户并不清楚代理和本体的区别，这 样做有两个好处。 1、 用户可以放心地请求代理，他只关心是否能得到想要的结果。 2、在任何使用本体的地方都可以替换成使用代理。</p>
<p>在 Java 等语言中，代理和本体都需要显式地实现同一个接口，一方面接口保证了它们会拥 有同样的方法，另一方面，面向接口编程迎合依赖倒置原则，通过接口进行向上转型，从而避开 编译器的类型检查，代理和本体将来可以被替换使用。</p>
<p>在 JavaScript 这种动态类型语言中，我们有时通过鸭子类型来检测代理和本体是否都实现了 setSrc 方法，另外大多数时候甚至干脆不做检测，全部依赖程序员的自觉性，这对于程序的健壮 性是有影响的。不过对于一门快速开发的脚本语言，这些影响还是在可以接受的范围内，而且我 们也习惯了没有接口的世界</p>
<h2 id="缓存代理"><a href="#缓存代理" class="headerlink" title="缓存代理"></a>缓存代理</h2><p>缓存代理可以为一些开销大的运算结果提供暂时的存储，在下次运算时，如果传递进来的参 数跟之前一致，则可以直接返回前面存储的运算结果。</p>
<p>为了节省示例代码，以及让读者把注意力集中在代理模式上面，这里编写一个简单的求乘积 的程序，请读者自行把它脑补为复杂的计算</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//先创建一个用于求乘积的函数： </span></span><br><span class="line"><span class="keyword">var</span> mult = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="built_in">console</span>.log( <span class="string">'开始计算乘积'</span> ); </span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">1</span>; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i &lt; l; i++ )&#123; </span><br><span class="line">        a = a * <span class="built_in">arguments</span>[i]; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> a; </span><br><span class="line">&#125;; </span><br><span class="line">mult( <span class="number">2</span>, <span class="number">3</span> ); <span class="comment">// 输出：6 </span></span><br><span class="line">mult( <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ); <span class="comment">// 输出：24 </span></span><br><span class="line"><span class="comment">//现在加入缓存代理函数：</span></span><br><span class="line"><span class="keyword">var</span> proxyMult = (<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> cache = &#123;&#125;; </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> ); </span><br><span class="line">        <span class="keyword">if</span> ( args <span class="keyword">in</span> cache )&#123; </span><br><span class="line">            <span class="keyword">return</span> cache[ args ]; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> cache[ args ] = mult.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;)(); </span><br><span class="line">proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ); <span class="comment">// 输出：24 </span></span><br><span class="line">proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ); <span class="comment">// 输出：24 </span></span><br><span class="line"><span class="comment">/*当我们第二次调用 proxyMult( 1, 2, 3, 4 )的时候，本体 mult 函数并没有被计算，proxyMult</span></span><br><span class="line"><span class="comment">直接返回了之前缓存好的计算结果。</span></span><br><span class="line"><span class="comment">通过增加缓存代理的方式，mult 函数可以继续专注于自身的职责——计算乘积，缓存的功能</span></span><br><span class="line"><span class="comment">是由代理对象实现的。*/</span></span><br></pre></td></tr></table></figure>

<h2 id="高阶函数动态创建"><a href="#高阶函数动态创建" class="headerlink" title="高阶函数动态创建"></a>高阶函数动态创建</h2><p>通过传入高阶函数这种更加灵活的方式，可以为各种计算方法创建缓存代理。现在这些计算方法被当作参数传入一个专门用于创建缓存代理的工厂中， 这样一来，我们就可以为乘法、加 法、减法等创建缓存代理，代码如下</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**************** 计算乘积 *****************/</span> </span><br><span class="line"><span class="keyword">var</span> mult = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">1</span>; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i &lt; l; i++ )&#123; </span><br><span class="line">        a = a * <span class="built_in">arguments</span>[i]; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> a; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">/**************** 计算加和 *****************/</span> </span><br><span class="line"><span class="keyword">var</span> plus = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> a = <span class="number">0</span>; </span><br><span class="line">    <span class="keyword">for</span> ( <span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="built_in">arguments</span>.length; i &lt; l; i++ )&#123; </span><br><span class="line">        a = a + <span class="built_in">arguments</span>[i]; </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">return</span> a; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="comment">/**************** 创建缓存代理的工厂 *****************/</span> </span><br><span class="line"><span class="keyword">var</span> createProxyFactory = <span class="function"><span class="keyword">function</span>(<span class="params"> fn </span>)</span>&#123; </span><br><span class="line">    <span class="keyword">var</span> cache = &#123;&#125;; </span><br><span class="line">    <span class="keyword">return</span> <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123; </span><br><span class="line">        <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.join.call( <span class="built_in">arguments</span>, <span class="string">','</span> ); </span><br><span class="line">        <span class="keyword">if</span> ( args <span class="keyword">in</span> cache )&#123; </span><br><span class="line">            <span class="keyword">return</span> cache[ args ]; </span><br><span class="line">        &#125; </span><br><span class="line">        <span class="keyword">return</span> cache[ args ] = fn.apply( <span class="keyword">this</span>, <span class="built_in">arguments</span> ); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;; </span><br><span class="line"><span class="keyword">var</span> proxyMult = createProxyFactory( mult ), </span><br><span class="line">proxyPlus = createProxyFactory( plus ); </span><br><span class="line">alert ( proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出：24 </span></span><br><span class="line">alert ( proxyMult( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出：24 </span></span><br><span class="line">alert ( proxyPlus( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出：10 </span></span><br><span class="line">alert ( proxyPlus( <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span> ) ); <span class="comment">// 输出：10</span></span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/24/node/%E5%89%8D%E7%AB%AF%E8%A7%86%E5%9B%BE%E6%A8%A1%E6%9D%BF/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/24/node/%E5%89%8D%E7%AB%AF%E8%A7%86%E5%9B%BE%E6%A8%A1%E6%9D%BF/" class="post-title-link" itemprop="url">前端视图模板</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-24 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-24T12:18:39+08:00">2021-10-24</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:19:16" itemprop="dateModified" datetime="2021-11-03T16:19:16+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>之前所讲的都是通过Koa来实现API操作，或者将数据持久化到数据库中，以上操作都是偏后端的操作。Koa是同时支持前后端操作的框架，下面主要介绍以下三个部分：1、静态资源托管的方式；2、动态模板渲染：使用模板引擎，结合数据库返回的内容，进行动态渲染；3、前后端分离的实践。</p>
<h1 id="静态服务器"><a href="#静态服务器" class="headerlink" title="静态服务器"></a>静态服务器</h1><p>静态服务器是通过静态HTTP服务器来提供HTML、JS、CSS文件及图片的托管服务的，Node.js世界里的koa-static、http-server等模块和常见的Apache、Nginx功能类似。</p>
<p>Koa里有一个koa-static模块，是官方提供的静态服务模块，</p>
<h1 id="模板引擎"><a href="#模板引擎" class="headerlink" title="模板引擎"></a>模板引擎</h1><p>前面说得是静态文件处理，但大多数网站是动态得，动态网站需要根据数据变化来进行最终得视图渲染，此时非常适合使用模板引擎。</p>
<p>编译（模板+数据） = HTML。</p>
<p>模板是一种采用复用思想实现得高级写法，其实现原理是：先定义模板，然后再使用的时候将模板和数据一起编译成模板文件。</p>
<p>模板的编译工具称为模板引擎，经典的模板引擎有：EJS、Pug、Nunjucks。</p>
<h1 id="Webpack"><a href="#Webpack" class="headerlink" title="Webpack"></a>Webpack</h1><p>以Node.js为基础的各个模块成为了前端发展各个方面的重要支柱，具体的示例如下：</p>
<p>资源处理：Data URLs；压缩：Uglify、JSMin、Csso；依赖管理：NPM、Bower；图片处理：Imagemin；模块系统：CommonJS、AMD、ES6 Modules；模块加载：RequireJS、JSpm、Sea、PostCSS。</p>
<p>模板引擎：Pug、Handlebars、Nunjucks；JavaScript友好语言：Coffee、Babel、TypeScript；构建：Grunt、Gulp、make；模块打包：Browserify、Webpack。</p>
<p>模块加载器：例如RequireJS，其核心原理是AMD规范，简单易用但缺少线上优化，所以必须依赖Grunt、Gulp等构建工具。  虽然不用管理模块加载，但是要掌握构建工具。而模块打包器则让一切变得简单，Webpack是最流行的模块打包器。</p>
<p>Webpack是一个JS应用程序的静态模块打包器，当Webpack处理应用程序时，它会递归构建一个依赖关系图，其中包含应用程序需要的每一个模块，然后将所有模块打包成一个或多个bundle文件。</p>
<h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><p>代码分割是Webpack最重要的特性，要点如下：1、在恰当的时机加载需要的模块；2、将第三方库与源码分离；3、实现样式分离；4、实现异步加载分离。</p>
<p>所有的资源都是模块，其中包含如下部分：Entry、Output、Loader、Plugin（包括CommonsChunkPlugin：提取公共模块；DefinePlugin：定义环境变量）、UglifyJsPlugin：实现代码压缩、ExtractTextPlugin：提取样式代码。</p>
<h3 id="css-loader"><a href="#css-loader" class="headerlink" title="css-loader"></a>css-loader</h3><p>在webpack中，可以基于css-loader，在JS文件中通过require的方式引入CSS。具体的实现过程如下：首先需要安装css-loader和style-loader（安装style-loader是为了在HTML中以style的方式嵌入CSS）。</p>
<p>实现要点如下：1、CSS代码被打包到JavaScript中，所有文件都是资源，都可以被打包；2、CSS代码被css-loader处理了；3、Webpack命令会按照配置文件进行编译。</p>
<p>这里很容易理解，首先要根据文件后缀来匹配处理方式。如果是CSS文件，会采用css-loader来处理，如果是less文件，就需要用less-loader来处理，通过less进行预编译的配置如下：</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">loaders: [</span><br><span class="line">    &#123;</span><br><span class="line">        test: /\.css$/, loader: 'style-loader!css-loader'</span><br><span class="line">    &#125;, </span><br><span class="line">    &#123;</span><br><span class="line">        test: /\.less$/, loader: 'style-loader!css-loader!less-loader'</span><br><span class="line">    &#125;,</span><br><span class="line">]</span><br></pre></td></tr></table></figure>

<h3 id="分割与优化"><a href="#分割与优化" class="headerlink" title="分割与优化"></a>分割与优化</h3><p>webpack-visualizer是Webpack的可视化资源分析工具，在构建Webpack时会自动计算出各个模块在项目工程中的依赖与分布情况，方便我们做出更精确的资源依赖和引用分析；例如：判断项目结构是否合理、依赖引用是否合理。</p>
<p>然而，大多数项目的依赖库体积特别大，内次开发过程中会重新读取和编译对应的依赖资源，且我们很少去修改第三方依赖的源码，为改进上述情况，可以进行以下配置：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//在webpack.config.js中增加以下代码，增加vendor选项，并使用webpack.optimize.CommonsChunkPlugin插件</span></span><br><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">'path'</span>);</span><br><span class="line"><span class="keyword">const</span> webpack = <span class="built_in">require</span>(<span class="string">'webpack'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">    entry: &#123;</span><br><span class="line">        main: <span class="string">'./src/index.js'</span>,</span><br><span class="line">        vendor: <span class="string">'debug'</span></span><br><span class="line">    &#125;,</span><br><span class="line">    output: &#123;</span><br><span class="line">        filename: <span class="string">'[chunkhash].[name].js'</span>,</span><br><span class="line">        path: path.resolve(__dirname, <span class="string">'dist'</span>),</span><br><span class="line">    &#125;,</span><br><span class="line">    plugins: [</span><br><span class="line">        <span class="keyword">new</span> webpack.optimize.CommonsChunkPlugin(&#123;</span><br><span class="line">            name: <span class="string">'vendor'</span>,</span><br><span class="line">            minChunks: <span class="literal">Infinity</span></span><br><span class="line">        &#125;)</span><br><span class="line">    ]</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>后面，我们需要在资源中加入external配置项，external可以把依赖资源声明为一个外部依赖，然后通过Script外链脚本引入HTML中。这也是我们早期进行页面开发时引入资源模式的一种翻版。可以告知Webpack，当遇到某类变量名时不用解析和编译至模块的内部文件，改用从外部变量中读取的方法能极大地提升编译速度，同时也能等好地利用CDN来实现缓存。</p>
<p>external的配置相对比较加单，只需要完成以下两步：</p>
<p>1、在页面中加入需要引入的lib地址</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"//cdn.bootcss.com/jquery.min.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"//cdn.bootcss.com/underscore.min.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/static/common/react.min.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/static/common/react-dom.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/static/common/react-router.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"/static/common/immutable.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>2、在webpack.config.js中加入external配置项</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.export = &#123;</span><br><span class="line">    externals: &#123;</span><br><span class="line">        <span class="string">'react-router'</span>: &#123;</span><br><span class="line">            amd: <span class="string">'react-router'</span>,</span><br><span class="line">            root: <span class="string">'ReactRouter'</span>,</span><br><span class="line">            commonjs: <span class="string">'react-router'</span>,</span><br><span class="line">            commonjs2: <span class="string">'react-router'</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        react: &#123;</span><br><span class="line">            amd: <span class="string">'react'</span>,</span><br><span class="line">            root: <span class="string">'React'</span>，</span><br><span class="line">            commonjs: <span class="string">'react'</span>,</span><br><span class="line">            commonjs2: <span class="string">'react'</span></span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="string">'react-dom'</span>: &#123;</span><br><span class="line">            amd: <span class="string">'react-dom'</span>,</span><br><span class="line">            root: <span class="string">'ReactDOM'</span>,</span><br><span class="line">            commonjs: <span class="string">'react-dom'</span>,</span><br><span class="line">            commonjs2: <span class="string">'react-dom'</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>配置此类文件前，构建资源包需要采用AMD、CommonJS、CMD进行兼容封装，即打包好得库已经是经过umd模式包装过得库。</p>
<p>优化Webpack构建可以带来很多便利，比如可以利用DllPlugin和DllReferencePlugin预编译资源模块；可以使用HappyPack多进程加速代码构建（生产环境也可以利用多进程加速代码构建）；可以使用webpack-paraller-uglify-plugin提升代码压缩混淆得打包效率。</p>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>Webpack得打包过程如下：1、从配置文件中找到入口；2、解析模块系统；3、解决依赖；4、处理模块依赖（读取、解析、解决）；5、合并所有使用得模块；6、合并模块系统得运行时环境；7、生成打包后得文件。</p>
<p>有了打包后得文件，就可以在浏览器中使用了，在浏览器中加载解析文件得过程如下：</p>
<p>1、通过script标签加载Webpack打包后得文件；2、加载模块运行时环境；3、加载入口文件；4、读取依赖；5、解决依赖；6、执行（带有依赖的）入口文件。</p>
<p>打包构建过程（合并、转换降级、优化、压缩）是由Webpack的Loaders（加载器）和plugins(插件)完成的，开发者只需要关心模块的编写就可以了。</p>
<h2 id="工具化"><a href="#工具化" class="headerlink" title="工具化"></a>工具化</h2><p>实际项目中，如果每个项目都使用Webpack，会导致Webpack配置项很难维护，而且不同项目配置项也不同。我们可以基于webpack设计一个前端开发工具，但配置项统一采用”约定大于配置”的思想，在项目内部尽可能少地进行配置。</p>
<p>YKit是去哪儿网开源的Node.js模块，是对webpack的封装。它用各种插件机制封装了各种类型应用的配置，只需要安装合适的插件就可以把一个基本的开发环境搭配好。Ykit的核心包括三部分：用户编写的模块+Ykit配置项+Ykit插件。</p>
<p>总的来说Ykit只是一个壳，真正的多样化构建依赖于插件，插件是一个个npm模块，它们将构建过程中的配置和功能进行封装，使得Ykit能满足多种多样的需求而不会冲突，并保持了开发的相对一致性。</p>
<p>优化webpack：1、webpack的构建速度慢，很多情况下是每次都还要去一个个寻找文件在资源树中的位置，这里我们在每次找到之后，生成一个hash缓存，下次再寻找时就可以直接按照存储的路径来了。</p>
<p>2、在第一次构建后，如果文件没有变动，就不会出发新的构建操作，因此Ykit只有在第一次启动时略慢，之后启动会很快，且部分改动也只会针对变动文件进行重新构建。</p>
<p>3、另一个webpack中很费时的是压缩资源吗，Ykit采用了多进程的压缩方式，使用Node.js中的cluster弄块实现了多进程并行处理。</p>
<h1 id="前端渲染"><a href="#前端渲染" class="headerlink" title="前端渲染"></a>前端渲染</h1><p>CSR客户端渲染是不需要服务端介入的，但其实从模板时代起就有SSR服务端渲染了。SSR是指采用React、Vue等现代框架，结合Node.js等服务端环境完成页面渲染的一种开发方式。</p>
<p>SSR的目标：1、SEO：搜索引擎优化；2、页面加载性能提升，特别是首屏加载速度；3、架构升级，兼容低版本浏览器。</p>
<p>阿里巴巴开源的Beidou框架：专注于React服务端渲染的同构框架，默认使用src/client目录存放客户端代码，使用src/app目录存放基于Egg.js的服务端代码。</p>
<p>Beidou基于Egg.js生态，具有可插拔的同构能力，其特点如下：</p>
<p>1、beidou-view-react是原有MVC架构中视图层的替代，使用React组件作为视图层模板，可以直接渲染React Component并输出到客户端；</p>
<p>2、beidou-webpack将Webpack集成到了框架中，可在开发阶段提供代码的编译和打包服务；</p>
<p>3、beidou-isomorphic是同构服务器端的React运行时环境；</p>
<p>4、服务器端支持CSS Modules；5、编写纯静态页面时，无须编写任何服务端代码。</p>
<p>Beidou已经简化了React和服务器端之间的组装和优化工作，开发者只需要关注三个点：React组件写法、Webpack配置和Egg.js用法。</p>
<h2 id="React服务端渲染"><a href="#React服务端渲染" class="headerlink" title="React服务端渲染"></a>React服务端渲染</h2><p>在Beidou这样的框架中，同构的意思是：一套代码既可以在服务端运行，又可以在客户端运行，这种同构更像是对语言的统一。</p>
<p>另一个著名的SSR框架是Next.js，它是一个纯粹为SSR应用开发的框架。Next.js式的组件写法如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Page</span>(<span class="params">props</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span> &#123;props.name&#125; <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Page.getInitialProps = <span class="keyword">async</span>(ctx) =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">Promise</span>.resolve(&#123;</span><br><span class="line">        name: <span class="string">'Egg + React + SSR'</span></span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> Page;</span><br></pre></td></tr></table></figure>

<p>Umi是一个轻量级的、可插拔的企业级React应用框架，它对于antd的集成更友好，在执行umi build命令后，dist目录下会生成umi.server文件，用于服务端渲染。</p>
<p>通过ReactDOMServer的renderToNodeStream分发，umi.server.js将被解析成Stream，然后通过ctx.body完成SSR。Umi SSR的特点如下：1、与框架无关，可以和任何Node.js Web框架集成，甚至可以集成Node.js自带的http模块；2、通过renderToNodeStream方法返回Stream，可以更好地基于Stream进行组装渲染。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/22/node/Koa%E7%BB%83%E4%B9%A0/" class="post-title-link" itemprop="url">Koa练习</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-22 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-22T12:18:39+08:00">2021-10-22</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:19:59" itemprop="dateModified" datetime="2021-11-03T16:19:59+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在RFC3986规范中，URL地址的匹配方式如下：</p>
<p>foo:(scheme部分)//cnodejs.org:3000(authority部分)/over/there(path部分)?name=ferret(query部分)#nose(fragment部分)</p>
<p>在Node中有一个模块parseurl专门用于解析URL地址。</p>
<p>HTTP采用请求、响应模型：浏览器或其他客户端发起请求，服务端给予响应。首先传输的就是HTTP头部信息，而头部信息通常被分为四部分：general header、request header、response header、entity header。对于开发者而言，无论是使用，还是性能调优，都必须掌握头部信息相关的内容。因为浏览器是依靠请求和响应中的头部信息来控制缓存的。</p>
<p>而Koa对HTTP头的处理分为：1、设置：ctx.header中无参数，则可用来获取所有的头部信息；2、获取：ctx.get用来获取特定的头部信息。</p>
<p>HTTP动词：基于RESTful架构，HTTP动词一共有5个，包括：GET、POST、PUT、PATCH、DELETE。在koa中可以使用ctx.method来获取HTTP动词。</p>
<p>HTTP状态码：是响应返回给浏览器的状态，客户端可以根据状态码及Content-Type值来进行渲染，网络请求部分会显示对应的状态码。</p>
<p>ctx.status = ctx.response.status;在Koa中，可以采用以下方式获取状态码并对其赋值。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"></span><br><span class="line">app.use(<span class="keyword">async</span> ctx =&gt; &#123;</span><br><span class="line">    ctx.status = <span class="number">404</span>;</span><br><span class="line">    ctx.type = <span class="string">"html"</span>;</span><br><span class="line">    ctx.body = <span class="string">"&lt;h1&gt;this is status demo&lt;/h1&gt;"</span></span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">上述代码作用：1、将状态码赋值为404；2、设置Content-Type的返回值类型为HTML类型；</span></span><br><span class="line"><span class="comment">3、设置具体的返回body为一段AHTML代码。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>Cookie是在HTTP下，服务端或脚本维护客户工作站上存在的一种信息形式，是由Web服务器保存在客户端上的小文本文件。</p>
<p>Cookie的处理流程如下：服务端向客户端发送Cookie，客户端的浏览器把Cookie保存起来，然后每次请求浏览器时将Cookie发送到服务端。在HTML文档被发送之前，Web服务器会通过通过传送HTTP包头的Set-Cookie信息把一个Cookie发送到用户的浏览器中。</p>
<p>在Node.js中，Cookie是通过response.writeHead被写入的。</p>
<h1 id="请求解析"><a href="#请求解析" class="headerlink" title="请求解析"></a>请求解析</h1><h2 id="获取参数"><a href="#获取参数" class="headerlink" title="获取参数"></a>获取参数</h2><p>1、获取具名参数：带冒号的占位参数是具名参数，一般常用于RestfulAPI中。koa的插件koa-router中内置了ctx.params，使用它可以轻松获取具名参数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">router.get(<span class="string">'/:id'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = <span class="string">'show user id = '</span> + ctx.params.id</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>2、解析请求体：在Node.js原生的http模块中，请求体要基于流的方式接收与解析。在koa中，解析请求体的中间件是koa-bodyparser。用法十分简单，示例代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bodyparser = <span class="built_in">require</span>(<span class="string">'koa-bodyparser'</span>)();</span><br><span class="line">app.use(bodyparser);</span><br><span class="line"><span class="comment">//上述代码在全局中间件中引入koa-bodyparser</span></span><br><span class="line"><span class="comment">//用法的示例代码如下：</span></span><br><span class="line">router.post(<span class="string">'/post'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = ctx.request.body;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>3、获取查询字符串：一般前端讲的query是querystring的简写，指的是查询字符串。在koa中，可以通过ctx.quert来获取查询字符串对象。</p>
<h2 id="body解析"><a href="#body解析" class="headerlink" title="body解析"></a>body解析</h2><p>处理请求体的模块非常多，这里我们以常用的koa-bodyparser为例，列出和它有依赖关系的模块。</p>
<p>原理：在HTTP的请求头中，有些动词会带请求体，我们这是需要根据不同的Content-Type来返回body。Koa通过ctx.body向浏览器写入响应，而ctx上用了body关键字，所以只能通过ctx.request.body来处理请求体。</p>
<p>koa-bodyparser中的默认配置是启动JSON支持，所以只要在请求前添加koa-bodyparser这个中间件就可以。</p>
<p>请求表单是工作中最常用到的，以POST为例，它通常会用于进行3种处理：1、通用表单上传；2、普通表单上传、3、文件上传。</p>
<p>1、通用表单指的是form-data，主要依赖koa-bodyparser和koa-multer。</p>
<p>koa-bodyparser默认支持form类型；而koa-multer是一个非常便于使用的文件上传模块，使用koa-multer可以解析body中的流，并将其保存为文件；koa-multer不会处理multipart/form-data以外的任何表单。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'koa-multer'</span>);</span><br><span class="line"><span class="keyword">const</span> upload = multer(&#123;<span class="attr">dest</span>: <span class="string">'uploads/'</span>&#125;);</span><br><span class="line"><span class="comment">//koa-multer的使用非常简单，只需要把upload内置的中间件挂载到路由上即可，这里可以使用 upload.any()方法</span></span><br><span class="line">router.post(<span class="string">'/post/formdata'</span>,upload.any(), (ctx) =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(ctx.req.files);</span><br><span class="line">    ctx.body = &#123;</span><br><span class="line">        status: &#123;</span><br><span class="line">            code: <span class="number">0</span>,</span><br><span class="line">            msg: <span class="string">'upload sucess'</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        data: &#123;</span><br><span class="line">            body: ctx.req.body,</span><br><span class="line">            files: ctx.req.files</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>2、普通表单对应的Content-Type是x-www-form-urlencoded。这样，直接用ctx.body = ctx.request.body即可进行解析。</p>
<p>3、文件上传：Koa中koa-multer中间件使用示例如下：</p>
<p>1、需要将multer的上传目录配置为根目录下的uploads目录；2、upload变量上的函数有array、singgle、fields等，这些函数均可用于处理文件上传；3、multer的原始做法是通过ctx.req.files来获取上传的文件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> router = <span class="built_in">require</span>(<span class="string">'koa-router'</span>)();</span><br><span class="line"><span class="keyword">const</span> multer = <span class="built_in">require</span>(<span class="string">'koa-multer'</span>);</span><br><span class="line"><span class="keyword">const</span> upload = multer(&#123;<span class="attr">dest</span>: <span class="string">'uploads/'</span>&#125;);</span><br><span class="line"></span><br><span class="line">router.prefix(<span class="string">'/upload'</span>);</span><br><span class="line"></span><br><span class="line">router.post(<span class="string">'/'</span>,upload.any(), (ctx) =&gt; &#123;</span><br><span class="line">    ctx.body = &#123;</span><br><span class="line">        status: &#123;</span><br><span class="line">            code: <span class="number">0</span>,</span><br><span class="line">            msg: <span class="string">'upload sucess'</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        data: &#123;</span><br><span class="line">            body: ctx.req.files</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="built_in">module</span>.exports = router;</span><br></pre></td></tr></table></figure>

<p>文本解析：koa-bodyparser默认只支持form和JSON两种格式的解析，当出现Content-Type:”text/plain”的时候是无法处理的，需要开启text支持。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">app.use(bodyparser(&#123;</span><br><span class="line">    enableTypes:[<span class="string">'json'</span>, <span class="string">'form'</span>, <span class="string">'text'</span>]</span><br><span class="line">&#125;));</span><br><span class="line"><span class="comment">//接着我们在routes/user.js里写一个/post/raw路由，其用法和普通表单解析请求体的用法一致，通过ctx.request.body来访问请求体</span></span><br><span class="line">router.post(<span class="string">'/post/raw'</span>, (ctx, next) =&gt; &#123;</span><br><span class="line">    ctx.body = ctx.request.body;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<h2 id="HTTP模块"><a href="#HTTP模块" class="headerlink" title="HTTP模块"></a>HTTP模块</h2><p>Node.js的SDK提供了对HTTP客户端请求的支持，通用的做法是使用http.request函数，它的底层是http.ClientRequest，即客户端请求。</p>
<p>其实有5个通用的HTTP请求模块，借用这些模块，可以简化请求处理流程。</p>
<p>request模块：是一个Node.js模块，为HTTP客户端，可用于实现HTTP响应流的转接、模拟表单提交、支持HTTP认证、实现OAuth登录、自定义请求头等。</p>
<p>superagent是一个轻量级的Node.js模块，它可读性好，且在所有的Node.js应用中，superagent的API设计最为人性化，其中有大量可用插件。</p>
<p>superagent也是著名的测试模块supertest的基础模块，值得学习。</p>
<p>got是一个更为简单易用的模块，不具备较强的通用性，但胜在小巧。</p>
<p>node-fetch模块：Fetch是XMLHttpRequest的替代技术。Fetch API提供了一个window.fetch()方法，我们可以用它来发起对远程资源的请求，该方法返回的是一个Promise对象，可对返回的结果进行检索。</p>
<p>axios：是一个基于Promise的HTTP模块，可以被同时用在浏览器和node中。</p>
<h1 id="API与中间件"><a href="#API与中间件" class="headerlink" title="API与中间件"></a>API与中间件</h1><p>API是纯I/O应用，这是Node.js最擅长的场景，如果API是从异构服务中获得并组装的，那么API组装部分使用Node来编写是非常合适的。前面说过的BFF和APIProxy完全可以体现。</p>
<p>koa.res.api是一个Koa中间件，要将它挂载到app对象上，该中间件支持多种API调用方式。 </p>
<p>1、直接返回API接口；2、返回带有状态的JSON数据；3、返回JSON API；4、异常情况下返回错误结果。</p>
<p>响应处理：API的定义是简单的，但对于响应结果的处理却很麻烦，由于JS为动态语言，因此会经常抛出Uncaught TypeError类的异常。</p>
<p>除了前端请求需要做响应处理，使用Node进行API聚合也需要进行响应处理，这是Node开发中相当重要的内容。比较常见的是使用Lodash的_.get方法，根据对象路径获取值；如果获取的值是undefined，则会赋予解析结果以默认值，有效减少异常处理。</p>
<p>另一种更好得方式是使用TypeScript，其作为一个静态语言，提前了类型检查的时机。在TypeScript里，接口的作用是为这些类型及为代码定义契约。根据接口信息，我们可以对值所具有的结构进行类型检查。</p>
<p>RESRTful API：RESRTful API是以HTTP为依托，还原了URL的本质，就是说一个URL就是一个资源，用于表示状态的变化，不包含其他任何动作。</p>
<p>API访问鉴权：传统的Web站点可以通过登录来结局用户鉴权问题，而API接口的安全问题可以由以下原则确保：1、有调用者身份；2、请求具有唯一性；3、请求的参数不能被篡改；4、请求有效事件，即API对应的令牌的有效期要长一些。</p>
<p>目前流行的鉴权方式主要有：JSON WebToken、OAuth。</p>
<h2 id="常用中间件"><a href="#常用中间件" class="headerlink" title="常用中间件"></a>常用中间件</h2><h3 id="会话"><a href="#会话" class="headerlink" title="会话"></a>会话</h3><p>会话是最常见的，用来保持客户端和服务器端的状态；HTTP本身是无状态协议，但有时我们需要记录用户的状态。会话是一种记录客户状态的机制，和Cookie类似，但Cookie是保存在客户端中的，会话保存在服务器中。</p>
<p>Koa中有许多与会话相关的模块：koa-session，是基于Cookie的简单会话实现；koa-generic-session：Session Store的抽象层，目标是让会话能够存储在Redis或MongoDB等自定义持久化存储中。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> session = <span class="built_in">require</span>(<span class="string">'koa-generic-session'</span>);</span><br><span class="line"><span class="keyword">const</span> RedisStore = <span class="built_in">require</span>(<span class="string">'koa-redis'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line">app.key = [<span class="string">'keys'</span>, <span class="string">'keyskeys'</span>]</span><br><span class="line"><span class="comment">//加入全局中间件</span></span><br><span class="line">app.use(session(&#123;</span><br><span class="line">    store: <span class="keyword">new</span> RedisStore(),</span><br><span class="line">    ttl: <span class="number">30</span> * <span class="number">60</span> * <span class="number">1000</span></span><br><span class="line">&#125;))</span><br><span class="line"><span class="comment">//在路由中可以直接通过ctx.session对后面的中间件进行操作</span></span><br><span class="line">app.use(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">switch</span> (ctx.path) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">'/get'</span>:</span><br><span class="line">            ctx.session.user = &#123; <span class="attr">name</span>: <span class="string">'i5ting'</span>&#125;</span><br><span class="line">            ctx.body = ctx.session.user</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">case</span> <span class="string">'/remove'</span>:</span><br><span class="line">            ctx.session = <span class="literal">null</span></span><br><span class="line">            ctx.body = <span class="string">"removed"</span></span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">8080</span>);</span><br><span class="line"><span class="comment">/*以上代码的三个要点：</span></span><br><span class="line"><span class="comment">1、依赖Redis，因此需要先启动Redis服务器；</span></span><br><span class="line"><span class="comment">2、通过ctx.session进行会话信息处理；</span></span><br><span class="line"><span class="comment">3、TTL是Session Store的超时事件，</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h3 id="ETag"><a href="#ETag" class="headerlink" title="ETag"></a>ETag</h3><p>ETag是前端缓存优化的重要部分，ETag在服务端生成后，客户端通过If-Match或If-None-Match条件判断请求，来验证资源是否被修改。</p>
<p>在Koa里，需要使用koa-conditional-get和koa-etag插件来提供ETag功能。</p>
<h3 id="验证码"><a href="#验证码" class="headerlink" title="验证码"></a>验证码</h3><p>OTP：动态口令，根据专门的算法每隔60秒生成的一个与事件相关的、不可预测的随机数字组合。</p>
<h3 id="限制访问频率"><a href="#限制访问频率" class="headerlink" title="限制访问频率"></a>限制访问频率</h3><p>限制访问频率是对抗暴力破解的有效手段，最简单也最好用的办法是利用Redis的expire命令。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/21/node/node%E4%B8%AD%E7%9A%84HTTP/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/21/node/node%E4%B8%AD%E7%9A%84HTTP/" class="post-title-link" itemprop="url">node中的HTTP</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-21 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-21T12:18:39+08:00">2021-10-21</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:20:36" itemprop="dateModified" datetime="2021-11-03T16:20:36+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>HTTP超文本传输协议：是互联网上应用最广泛的一种网络协议，最初设计HTTP是为了提供一种发布和接受HTML页面的方法。通过HTTP与HTTPS请求的资源由统一资源标识符URL来标识。</p>
<p>HTTP协议是一个无状态的协议，请求在每次响应后就会结束。</p>
<h2 id="Stream"><a href="#Stream" class="headerlink" title="Stream"></a>Stream</h2><p>Stream主要是对I/O输入输出的抽象，它将请求响应过程抽象成一个流并在内存中传递，便于大文件处理，能够提高扩展性。其核心有以下两点：1、可以将一个大型系统拆分成一些很小的部分作为流的一部分，通过将它们任意组装，甚至可以实现高级的流程控制。2、不同部分之间的数据通过管道传递，在UNIX、Linux中可以使用“|”符号实现流。</p>
<p>Strem本身是一个抽象接口，Node.js中有很多对象实现了该接口，和UNIX类似，在Node.js中，流模块的基本操作符是.pipe，通过它可以直接将上一步的结果作为下一步的输入。Stream在Node中继承自EventEmitter，并有多种实现形式。</p>
<p>Node中Stream的五种流操作类型：1、Readable：可读操作类型2，该数据可以被传送至其他流中，只要调用pipe方法即可；2、Writable：可写操作类型，只能流进不能流出；Duplex：可读可写操作类型；Transform：转换类型，可以写入数据，然后读出结果；classic：经典接口。</p>
<p>原理：Stream的精髓是将上一个输出作为下一个输入，这和Linix中管道的功能一样；将上一个输出结果作为下一个输入参数。</p>
<p>请求传递和过滤是Web Server中非常重要的功能，JaveEE容器Tomcat中的Servlet过滤器、OkHttp都是基于责任链模式实现的，Node中的Stream机制也一样。Stream是通过管道进行操作的，要求输入和输出的内容必须是流，否则需要通过through2这样的模块进行转换。Node中request、response对象都是继承自Stream的。</p>
<p>文件操作：大文件读写是非常麻烦的，这是选择Stream作为读写方法是最好的，这也是HTTP中广泛使用流式方法处理文件的原因。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> source = fs.readFileSync(<span class="string">'/path/to/source'</span>, &#123;<span class="attr">encoding</span>: <span class="string">'utf8'</span>&#125;);</span><br><span class="line">fs.writeFileSync(<span class="string">'/path/to/dest'</span>, source);</span><br><span class="line"></span><br><span class="line"><span class="comment">//上面代码等价于：</span></span><br><span class="line">fs.createReadStream(<span class="string">'/path/to/source'</span>).pipe(fs.createWriteStream(<span class="string">'/path/to/dest'</span>))</span><br></pre></td></tr></table></figure>

<p>转换模块：Gulp是Node.js世界中基于Stream实现的最著名的流式高效构建工具。在执行效率方面做到了机制，唯一比较麻烦的是基于流的调试和插件开发。</p>
<p>gulp-debug模块可以非常简单地打印出在当前处理过程中使用的文件信息，写法基于经典gulp插件，且使用through2模块做转换，</p>
<p>through2将输入流转成了更具可读性的文件信息，并将其作为参数，可以方便开发者使用。</p>
<p>HTTP请求：请求和响应都继承自Stream，所以可以直接通过pipe方法进行组装。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> app = http.createServer(<span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="string">'/remote'</span> === req.url) &#123;</span><br><span class="line">        res.writeHead(<span class="number">200</span>, &#123;<span class="string">'Content-Type'</span>: <span class="string">'text/plain'</span>&#125;)</span><br><span class="line">        <span class="keyword">return</span> res.end(<span class="string">'Hello Remote Page\n'</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        proxy(req, res);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">proxy</span> (<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> options = &#123;</span><br><span class="line">        host : req.host,</span><br><span class="line">        port : <span class="number">3000</span>,</span><br><span class="line">        headers : req.headers,</span><br><span class="line">        path : <span class="string">'/remote'</span>,</span><br><span class="line">        agent : <span class="literal">false</span>,</span><br><span class="line">        method : <span class="string">'GET'</span></span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">let</span> httpProxy = http.request(options, (response) =&gt; &#123;</span><br><span class="line">        response.pipe(res);</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    req.pipe(httpProxy);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">app.listen(<span class="number">3000</span>, <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> PORT = app.address().port;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'Server running at http://127.0.0.1:$&#123;PORT&#125;/'</span>);</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>

<p>以上代码执行的要点如下：1、变量httpProxy是http.request()函数的返回值；2、通过req.pipe(httpProxy)操作，req就拥有了新的res(即HTTP代理请求的响应)，也就是最终返回的是response。3、HTTP代理完成了一次完整的HTTP请求过程，响应交由res返回，于是原来的请求相当于透传了HTTP代理的请求和响应。</p>
<p>http.request通过pipe得到的返回值和res是一样的。</p>
<h2 id="EventEmitter"><a href="#EventEmitter" class="headerlink" title="EventEmitter"></a>EventEmitter</h2><p>EventEmitter是Node中典型的基于观察者设计模式实现的类，它是对事件触发和事件监听器的封装。</p>
<p>所有的Stream对象都是EventEmitter的示例，而响应和请求属于Strem对象，所以也是EventEmitter的示例，并继承了EventEmitter和Stream的事件，常见的事件有：1、data：有数据可读时触发；2、end：没有更多数据可读时触发；3、error：接收和写入过程中发生错误时触发；4、finish：所有数据已被写入底层系统时触发。</p>
<p>HTTP模块源码如下：</p>
<h2 id="HTTPS"><a href="#HTTPS" class="headerlink" title="HTTPS"></a>HTTPS</h2><p>HTTPS在传输过程中主要使用SSL/TLS进行加密，是目前最安全的方式。</p>
<h2 id="代理"><a href="#代理" class="headerlink" title="代理"></a>代理</h2>
      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/20/node/Koa%E6%A1%86%E6%9E%B6%E5%85%A5%E9%97%A8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/20/node/Koa%E6%A1%86%E6%9E%B6%E5%85%A5%E9%97%A8/" class="post-title-link" itemprop="url">koa框架入门</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-20 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-20T12:18:39+08:00">2021-10-20</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:19:47" itemprop="dateModified" datetime="2021-11-03T16:19:47+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>简单的koa框架使用示例如下代码，我们写一个简单的中间件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"></span><br><span class="line"><span class="comment">//日志中间件</span></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> start = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'[logger middleware] before await...'</span>);</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'[logger middleware] after await...'</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> ms = <span class="keyword">new</span> <span class="built_in">Date</span>() - start;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'$&#123;ctx.method&#125; $&#123;ctx.url&#125; - $&#123;ms&#125;ms'</span>);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//响应</span></span><br><span class="line">app.use(<span class="keyword">async</span> ctx =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'[response middleware] response...'</span>);</span><br><span class="line">    ctx.body = <span class="string">'Hello Koa2'</span></span><br><span class="line">&#125;)</span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">输出如下：</span></span><br><span class="line"><span class="comment">[logger middleware] before await...</span></span><br><span class="line"><span class="comment">[response middleware] response...</span></span><br><span class="line"><span class="comment">[logger middleware] after await...</span></span><br><span class="line"><span class="comment">GET / -7ms</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">这三处日志的输出很明显是在logger中间件的await next()代码前后进行处理的，通过await next()将执行权交给下一个中间件，即response中间件.</span></span><br><span class="line"><span class="comment">于是代码就截止async函数变成顺序执行了。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>要点：1、中间件支持async函数+箭头函数；2、参数除了next参数，还有上下文ctx参数，作为显式的上下文。</p>
<h2 id="框架演进"><a href="#框架演进" class="headerlink" title="框架演进"></a>框架演进</h2><p>Connect：是一个用Node.js编写的可扩展的HTTP服务器端框架，最早的Node服务端框架。</p>
<p>1、基于http.createServer；createServer的参数是独立的，而Connect里通过app.use()函数可以挂载多个插件式的中间件。</p>
<p>2、Connect提供了一个独立的app层，通过app.use函数来挂载中间件。提供了可变部分的插件化，而将不可变部分固定。</p>
<p>app.use的中间件是有顺序的；且中间件可分类，分为全局的和局部的（路由中生效的）；中间件的定义方法是function(req, res){}。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> connect = <span class="built_in">require</span>(<span class="string">'connect'</span>);</span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> app = connect();</span><br><span class="line"></span><br><span class="line"><span class="comment">//对响应进行gzip压缩</span></span><br><span class="line"><span class="keyword">var</span> compression = <span class="built_in">require</span>(<span class="string">'compression'</span>);</span><br><span class="line">app.use(compression());</span><br><span class="line"><span class="comment">//在浏览器缓存里存取会话状态</span></span><br><span class="line"><span class="keyword">var</span> cookieSession = <span class="built_in">require</span>(<span class="string">'cookie-session'</span>);</span><br><span class="line">app.use(cookieSession(&#123;</span><br><span class="line">    keys: [<span class="string">'secret1'</span>, <span class="string">'secret2'</span>]</span><br><span class="line">&#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">//解析urlencoded的请求体，并赋值给req.body</span></span><br><span class="line"><span class="keyword">var</span> bodyParser = <span class="built_in">require</span>(<span class="string">'body-parser'</span>);</span><br><span class="line">app.use(bodyParser.urlencoded(&#123;<span class="attr">extended</span>: <span class="literal">false</span>&#125;));</span><br><span class="line"></span><br><span class="line"><span class="comment">//响应所有请求</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span>(<span class="params">req, res</span>) </span>&#123;</span><br><span class="line">    res.end(<span class="string">'Hello from Connect!\n'</span>);</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">//启动服务器</span></span><br><span class="line">http.createServer(app).listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<p>Express的使用实例和Connect基本一样，只有模块引用不同。Express的启动方式有以下两种：1、通过内置的app.listen使用；2、使用http模块http.createServer(app)，可以完成更多定制功能。</p>
<p>和Express类似，KOA也对原始的http模块进行了抽象封装，只是中间件形式不一样。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="comment">//响应所有请求</span></span><br><span class="line">app.use(<span class="keyword">async</span> ctx =&gt; &#123;</span><br><span class="line">    ctx.body = <span class="string">'Hello Koa 2'</span>;</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> server = http.createServer(app.callback());</span><br><span class="line">server.listen(<span class="number">3000</span>);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">很明显，Koa把http.createServer里的内容封装到了app.callback()里。</span></span><br><span class="line"><span class="comment">app是Koa对象；app.use里使用的是Koa中间件写法；</span></span><br><span class="line"><span class="comment">app.callback()把app.use里的内容转成了function(req, res)&#123;...&#125;</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h1 id="中间件"><a href="#中间件" class="headerlink" title="中间件"></a>中间件</h1><p>中间件是框架的扩展机制，主要用于抽象HTTP请求过程；在单一请求响应过程中加入中间件，可以更好地应付复杂的业务逻辑。</p>
<p>KoaV2最重要的特性是支持async函数，且同时支持如下3中不同类型函数的中间件。</p>
<p>1、通用函数中间件，是最常用的。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="comment">//日志</span></span><br><span class="line">app.use(<span class="function">(<span class="params">ctx, next</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> start = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    <span class="keyword">return</span> next().then(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> ms = <span class="keyword">new</span> <span class="built_in">Date</span>() - start;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">'$&#123;ctx.method&#125; $&#123;ctx.url&#125; - $&#123;ms&#125;ms'</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//响应</span></span><br><span class="line">app.use(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">    ctx.body = <span class="string">'Hello Koa'</span>;</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<p>(ctx, next) =&gt; {}，它有两个参数，ctx为上下文，包括所有的请求和响应；而next()是确定当前中间件是否将请求向下一个中间件放行的函数。</p>
<p>2、生成器函数</p>
<p>生成器函数中间件被co.wrap包裹并转换；其参数中多了ctx；从该中间件跳转下一中间件是通过：yield next()函数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="comment">//日志</span></span><br><span class="line">app.use(co.wrap(<span class="function"><span class="keyword">function</span> *(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> start = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    <span class="keyword">yield</span> next();</span><br><span class="line">    <span class="keyword">const</span> ms = <span class="keyword">new</span> <span class="built_in">Date</span>() - start;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'$&#123;ctx.method&#125; $&#123;ctx.url&#125; - $&#123;ms&#125;ms'</span>);</span><br><span class="line">&#125;));</span><br><span class="line"><span class="comment">//响应</span></span><br><span class="line">app.use(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">    ctx.body = <span class="string">'Hello Koa'</span>;</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<p>3、async函数中间件</p>
<p>await可以无缝调用异步Promise方法，更好地向后兼容。async函数跳转到下一个中间件时，需要使用await next()。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> Koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> Koa();</span><br><span class="line"><span class="comment">//日志</span></span><br><span class="line">app.use(<span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> start = <span class="keyword">new</span> <span class="built_in">Date</span>();</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">    <span class="keyword">const</span> ms = <span class="keyword">new</span> <span class="built_in">Date</span>() - start;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'$&#123;ctx.method&#125; $&#123;ctx.url&#125; - $&#123;ms&#125;ms'</span>);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//响应</span></span><br><span class="line">app.use(<span class="function"><span class="params">ctx</span> =&gt;</span> &#123;</span><br><span class="line">    ctx.body = <span class="string">'Hello Koa'</span>;</span><br><span class="line">&#125;);</span><br><span class="line">app.listen(<span class="number">3000</span>);</span><br></pre></td></tr></table></figure>

<h2 id="上下文对象"><a href="#上下文对象" class="headerlink" title="上下文对象"></a>上下文对象</h2><p>在web应用中，Koa的上下文对象ctx是一次完整的HTTP请求的上下文，贯穿这个请求的生命周期。请求会经过N层中间件的拦截，唯一共享的就是这个上下文对象。它主要封装了request对象和response对象，并提供了一些帮助开发者编写业务逻辑的方法，我们可以在ctx.request和ctx.response中很方便地访问这些方法。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//这里统一把上下文定为ctx</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>;<span class="comment">//上下文对象</span></span><br><span class="line">    <span class="keyword">this</span>.request;</span><br><span class="line">    <span class="keyword">this</span>.response;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">//每个请求至少贯穿一个中间件</span></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params">next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>;</span><br><span class="line">    ctx.a = <span class="number">1</span>;</span><br><span class="line">    <span class="keyword">yield</span> next();</span><br><span class="line">    <span class="built_in">console</span>.log(ctx.a)<span class="comment">//此时打印2</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.use(<span class="function"><span class="keyword">function</span> *(<span class="params">next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> ctx = <span class="keyword">this</span>;</span><br><span class="line">    ctx.a = <span class="number">2</span>;</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">由于第二个中间件中没有next命令，所以结束响应，返回第一个中间件next()后面。</span></span><br><span class="line"><span class="comment">ctx的生命周期是贯穿整个HTTP请求过程的，在ctx上绑定内容不是一个好的做法，但适当地在ctx上下文绑定某些内容是必要的，</span></span><br><span class="line"><span class="comment">例如：日志中间件，ctx.render()函数。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>ctx上常用的对象有request、response、req、res等，其中request和response是Koa内置的对象，是对HTTP的实用扩展；而req和res是在http.createServer回调函数里注入的，即未经加工的原生内置对象。</p>
<p>之前说过，Koa提供的中间件机制是对http.createServer回调进行抽象；也就是说，app里被注入了http.createServer的回调函数，核心是app.callback()实现。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//使用方法如下：</span></span><br><span class="line"><span class="keyword">const</span> server = http.createServer(app.callback());</span><br><span class="line"></span><br><span class="line"><span class="comment">//callback的实现代码如下：在Koa源码application.js中。</span></span><br><span class="line">callback() &#123;</span><br><span class="line">    <span class="keyword">const</span> fn = compose(<span class="keyword">this</span>.middleware);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>.listeners(<span class="string">'error'</span>).length) <span class="keyword">this</span>.on(<span class="string">'error'</span>, <span class="keyword">this</span>.onerror);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">const</span> handleRequest = <span class="function">(<span class="params">req, res</span>) =&gt;</span> &#123;</span><br><span class="line">        res.statusCode = <span class="number">404</span>;</span><br><span class="line">        <span class="keyword">const</span> ctx = <span class="keyword">this</span>.createContext(req, res);</span><br><span class="line">        <span class="keyword">const</span> onerror = <span class="function"><span class="params">err</span> =&gt;</span> ctx.onerror(err);</span><br><span class="line">        <span class="keyword">const</span> handleResponse = <span class="function"><span class="params">()</span> =&gt;</span> respond(ctx);</span><br><span class="line">        onFinished(res, onerror);</span><br><span class="line">        <span class="keyword">return</span> fn(ctx).then(handleResponse).catch(onerror);</span><br><span class="line">    &#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">return</span> handleRequest;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//其中的核心const ctx = this.createContext(req, res);表示进行了绑定，其源码如下：</span></span><br><span class="line">createContext(req, res) &#123;</span><br><span class="line">    <span class="keyword">const</span> context = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.context);</span><br><span class="line">     <span class="comment">// 使用Object.create方法是为了继承this.context但在增加属性时不影响原对象;继承属性，且不影响原对象</span></span><br><span class="line">    <span class="keyword">const</span> request = context.request = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.request);<span class="comment">//Koa内置的request对象</span></span><br><span class="line">    <span class="keyword">const</span> response = context.response = <span class="built_in">Object</span>.create(<span class="keyword">this</span>.response);<span class="comment">//Koa内置的response对象</span></span><br><span class="line">    </span><br><span class="line">     <span class="comment">// 下面的操作是为了把ctx中的属性与request、response中的属性联系起来，其实就是将后两者中定义的方法挂载到ctx上</span></span><br><span class="line">    <span class="comment">//这样，我们后续就可以在ctx中用简洁的写法来访问各种属性了</span></span><br><span class="line">    context.app = request.app = response.app = <span class="keyword">this</span>;<span class="comment">//app自身</span></span><br><span class="line">    context.req = request.req = response.req = req;<span class="comment">//赋值为原始HTTP回调函数里的req对象</span></span><br><span class="line">    context.res = request.res = response.res = res;<span class="comment">//赋值为原始HTTP回调函数里的res对象</span></span><br><span class="line">    request.ctx = response.ctx = context;</span><br><span class="line">    request.response = response;</span><br><span class="line">    response.request = request;</span><br><span class="line">    context.originalUrl = request.originalUrl = req.url;<span class="comment">//originalUrl是最初的URL地址</span></span><br><span class="line">    context.cookies = <span class="keyword">new</span> Cookies(req, res, &#123;</span><br><span class="line">        keys: <span class="keyword">this</span>.keys,</span><br><span class="line">        secure: request.secure</span><br><span class="line">    &#125;);<span class="comment">//context.cookies是浏览器cookie封装</span></span><br><span class="line">    request.ip = request.ips[<span class="number">0</span>] || req.socket.remoteAddress || <span class="string">""</span>;</span><br><span class="line">    context.accept = request.accept = accepts(req);</span><br><span class="line">    context.state = &#123;&#125;;</span><br><span class="line">    <span class="comment">//context.state = &#123;&#125;约定了一个中间件的公用存储空间，可以存储一些数据。</span></span><br><span class="line">    <span class="keyword">return</span> context;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="request和response"><a href="#request和response" class="headerlink" title="request和response"></a>request和response</h2><p>为了使用方便，许多上下文属性和方法都被委托代理到了Koa的ctx.request或ctx.response上。</p>
<p>基于以上关系，想要对Stream进行扩展就十分容易了，分别在request.js和response.js上对处理方式进行扩展。</p>
<p>而对于ctx.request和ctx.response没有实现的功能，可以通过扩展ctx.req和ctx.res来实现。这两者是更低级别的API，是http.createServer的回调函数传进来的参数，通过它们可以实现更多功能。</p>
<p>例如：koa-bigpipe中间件就是通过原始的res.write来实现BigPipe分块写入功能的。http模块的res.write()默认是支持分块传输的，所以遵循Koa中间件约定并提供ctx.write和ctx.end方法，能够更好地以Koa的方式编写BigPipe。</p>
<p>Bigpipe适用场景：</p>
<p>如果一个页面，在服务端渲染中，数据源比较多的情况下，我们需要等待所有的请求都返回数据才进行html拼接并返回，这样我们页面最终渲染的速度就限制在最迟返回数据的请求上了。</p>
<p>那针对上述数据源较多的情况，还有优化的方案吗？答案就是Bigpipe。</p>
<p>Bigpipe是一种采用流的方式对页面进行渲染的机制，在浏览器请求页面时，打开管道后持续对页面的块进行输出。块A、B、C拼装好块之后直接通过开始建立的管道输出到页面中，这样页面的最终输出就不需要依赖最后一个块的拼装时间了。</p>
<h3 id="浏览器交互"><a href="#浏览器交互" class="headerlink" title="浏览器交互"></a>浏览器交互</h3><p>Koa框架与浏览器交互的方式主要是让服务器对浏览器进行响应，有三种可用方法。</p>
<p>1、ctx.body(Koa内置)：</p>
<p>ctx.body能够以最精简的代码实现最多的功能，返回文本、HTML输出或者JSON。</p>
<p>工作原理：1、根据body的类型设置对应Content-Type；2、根据Content-Type调用res.write或res.end，将数据写入浏览器。</p>
<p>2、ctx.redirect</p>
<p>浏览器重定向只有两种情况，向前重定向和向后重定向，</p>
<p>3、ctx.render是渲染模板适用的方法，有两个参数：模板和数据。该方法主要用于将模板编译成HTML并写入浏览器。</p>
<p>Koa v1的中间件机制其实就是将compose([f1,f2,…,fn])转化成fn(…f2(f1(noop())))，最终的返回值是一个生成器函数。而Koa v2的中间件机制是将compose([f1,f2,…,fn])转化为f1(…f(n-1)(fn(noop())))，最终的返回值是一个function (context, next) {}。即使返回值是function *(next){}，也会被转换为function(context, next) {}，这是convert的向后兼容特性。</p>
<h2 id="常用中间件"><a href="#常用中间件" class="headerlink" title="常用中间件"></a>常用中间件</h2><p>分为三类：Pre-Request：通常用于改写请求的原始数据；Request/Response：大部分中间件，功能各异；Post-Response：进行全局异常处理，改写响应数据之类的。</p>
<p>koa-compress：实现gzip压缩的中间件；koa-conditional-get和koa-etag：提供ETag使用的中间件；koa-favicon：是简单的Favicon中间件；koa-static：提供静态HTTP资源托管的中间件。</p>
<h1 id="路由"><a href="#路由" class="headerlink" title="路由"></a>路由</h1><p>koa框架是小而美的设计，本身没有内置路由，但是允许开发者使用各类插件实现功能。Koa中最常使用的是koa-router插件。</p>
<p>路由的本质是对ctx.path进行抽象，其实现原理：1、定义路由；2、路由的路径会被转译成正则表达式；3、匹配请求，使用中间件进行处理。</p>
<p>koa-router模块的核心是路由解析，使用的是path-to-regexp模块，与Express所使用的模块一致。</p>
<p>路由的四种形式：1、普通路由，如：’/users’；2、具名路由，如：’/user/:id’；具名路由由参数通过ctx.params来获取；</p>
<p>3、正则路由，如：’/^/commits/(\w+)(?:..(\w+))?$/‘</p>
<p>4、嵌套路由：即不同实体之间是一对多的关系，是上面多种路由形式的组合。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//路由也是中间件，其定义方法如下：</span></span><br><span class="line">router.use([path], middleware, [...]) =&gt; Router;</span><br><span class="line"><span class="comment">//简单的路由实例如下：</span></span><br><span class="line">router.get(<span class="string">'/users/:id'</span>,</span><br><span class="line">           <span class="function"><span class="keyword">function</span> (<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> User.findOne(ctx.params.id).then(<span class="function"><span class="keyword">function</span>(<span class="params">user</span>) </span>&#123;</span><br><span class="line">        ctx.user = user;</span><br><span class="line">        <span class="keyword">return</span> next();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;,<span class="function"><span class="keyword">function</span> (<span class="params">ctx</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(ctx.user);</span><br><span class="line">    <span class="comment">// =&gt; &#123;id:17, name:"Alex"&#125;</span></span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">在该例子中，第一个中间件根据ID查询到的用户，先被绑定到了ctx对象上；然后交给了下一个中间件进行处理。</span></span><br><span class="line"><span class="comment">接着，第二个中间件根据第一个中间件产生的ctx.user完成某些内容，最终决定返回何值。</span></span><br><span class="line"><span class="comment">由多种中间件组成的路由可以完成以下事情：</span></span><br><span class="line"><span class="comment">1、解耦代码，将代码拆分得更细致；</span></span><br><span class="line"><span class="comment">2、实现模块化，通过组装中间件来完成逻辑；</span></span><br><span class="line"><span class="comment">3、实现权限类、日志类得AOP（面向切面编程）</span></span><br><span class="line"><span class="comment">4、实现与Hook类似的抽象</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h2 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h2><p>按照koa-generator里的选型，Koa默认使用的视图模板引擎仍然是Pug，koa-views是最好用的一个视图模块。</p>
<h2 id="静态服务模块"><a href="#静态服务模块" class="headerlink" title="静态服务模块"></a>静态服务模块</h2><p>一般我们使用koa-static模块来托管静态资源。使用该模块，我们可以将前端常用的JS、CSS、JSON均通过本地静态服务器来提供。而静态资源托管的具体是如何实现的呢：koa-static依赖两个模块，分别为debug和koa-send。</p>
<p>koa-static源码的index.js代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> send = <span class="built_in">require</span>(<span class="string">'koa-send'</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">serve</span> (<span class="params">root, opts</span>) </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">serve</span> (<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">await</span> next();</span><br><span class="line">        <span class="keyword">if</span> (ctx.method !== <span class="string">'HEAD'</span> &amp;&amp; ctx.method !== <span class="string">'GET'</span>) <span class="keyword">return</span> </span><br><span class="line">        <span class="keyword">if</span> (ctx.body !== <span class="literal">null</span> || ctx.status !== <span class="number">404</span>) <span class="keyword">return</span> <span class="comment">//eslint-disable-line</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">await</span> send(ctx, ctx.path, opts)</span><br><span class="line">        &#125; <span class="keyword">catch</span>(err) &#123;</span><br><span class="line">            <span class="keyword">if</span> (err.status !== <span class="number">404</span>) &#123;</span><br><span class="line">                <span class="keyword">throw</span> err;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由上面的示例可以看出，其中的核心是send代码，即koa-send提供的。其实为文件提供HTTP服务是非常简答的，而koa-send就是做的这件事情：提供一个相对完善且小而美的处理静态文件的Koa中间件</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//其源码示例如下：</span></span><br><span class="line"><span class="keyword">if</span> (!ctx.type) ctx.type = type(path, encodingExt);</span><br><span class="line">ctx.body = fs.createReadStream(path);</span><br><span class="line"></span><br><span class="line"><span class="comment">//其中的ctx.type是根据文件后缀来设置Content-Type的便捷方法</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">type</span>(<span class="params">file, ext</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> ext !== <span class="string">''</span> ? extname(basename(file, ext)) : extname(file);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">基本原理总结如下：</span></span><br><span class="line"><span class="comment">1、设置Content-Type，可通过文件后缀机型设置；</span></span><br><span class="line"><span class="comment">2、以Stream的形式为ctx.body赋值，这种方式比fs.readFileSync更高效。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>koa-mount：将中间件挂载到指定路径的Koa中间件，它可以挂载任意Koa中间件；而koa-static是Koa中间件，所以koa-static与koa-mount结合就可以实现和express一样的静态服务器带有请求前缀的功能。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/19/node/%E5%BC%82%E6%AD%A5%E4%B8%8Easync/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/19/node/%E5%BC%82%E6%AD%A5%E4%B8%8Easync/" class="post-title-link" itemprop="url">异步与async</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-19 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-19T12:18:39+08:00">2021-10-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-05 16:36:29" itemprop="dateModified" datetime="2021-11-05T16:36:29+08:00">2021-11-05</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>在Node.js中，所有函数都是异步得，性能虽然好，但是容易造成回调地狱。在Node中，为解决API级别得回调地狱问题，引入了用于流程控制得部分：异步流程控制。</p>
<p>在异步流程控制方面做出得结果有以下：1、Thunk、Promise/A+规范落地；2、在ES6中支持Generator与co模块；3、async函数。</p>
<h1 id="异步调用"><a href="#异步调用" class="headerlink" title="异步调用"></a>异步调用</h1><p>Ajax和Node是两种典型得异步实现方案：Ajax的核心是XMLHttpRequest对象，Node.js的核心是EventLoop。</p>
<p>Ajax定义好请求和回调函数后，剩下的事情就交由XHR处理，XHR与服务器交互会产生时间差异，所以异步操作能很好解决该问题。</p>
<p>Node.js异步的核心是EventLoop，我们调用Node.jsAPI方法时，它会将具体操作和回调函数交给EventLoop来执行，而EventLoop维护了一个回调函数队列，异步函数执行时，回调函数会被放入这个队列，JS引擎直到异步函数执行完成后，才会开始处理EventLoop。</p>
<p>EventLoop是用C++编写的libuv库，其采用了异步和事件驱动的编程风格，主要功能是为开发人员提供一套基于I/O通知的回调函数，实际上是由操作系统处理的。</p>
<p>综上所述，无论Ajax还是Node，其都是借助中间层来进行实际操作的，我们无序过多关注底层就可以简单地完成功能开发。</p>
<h2 id="简单实践"><a href="#简单实践" class="headerlink" title="简单实践"></a>简单实践</h2><p>这里我们简单写一个找出以KOA开头的文件的函数，这里会用到fs.readdir(path, callback)，这样一个API。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//异步写法如下：</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="keyword">const</span> path = <span class="string">'.'</span>;</span><br><span class="line"></span><br><span class="line">fs.readdir(path, <span class="function"><span class="keyword">function</span> (<span class="params">err, files</span>) </span>&#123;</span><br><span class="line">   files.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">file</span>) </span>&#123;</span><br><span class="line">       <span class="keyword">if</span> (<span class="regexp">/^koa/</span>.test(file)) &#123;</span><br><span class="line">           <span class="built_in">console</span>.log(file);</span><br><span class="line">       &#125;</span><br><span class="line">   &#125;); </span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//在上例中，获取文件的结果是在回调函数中实现的，很明显为异步写法</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//同步写法如下：</span></span><br><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> files = fs.readdirSync(<span class="string">'.'</span>)</span><br><span class="line">files.forEach(<span class="function"><span class="keyword">function</span> (<span class="params">file</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="regexp">/^koa/</span>.test(file)) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(file);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">//在同步写法中，首先获取文件结果，然后遍历文件并打印结果。</span></span><br></pre></td></tr></table></figure>

<h2 id="事件处理"><a href="#事件处理" class="headerlink" title="事件处理"></a>事件处理</h2><p>Node中有两种事件处理方式，分别是回调和事件发射器。callback使用错误优先的回调方式，而事件发射器则是事件驱动里的事件发射。</p>
<h3 id="错误优先"><a href="#错误优先" class="headerlink" title="错误优先"></a>错误优先</h3><p>定义错误优先的回调方式只需要注意两条规则：1、回调函数的第一个参数返回的是error对象，若发生错误，则该对象会作为第一个参数返回；2、回调函数的第二个参数返回的是所有成功响应的结果数据。结果正常时，error就会被设置为null，在第二个参数处返回正确的响应。</p>
<p>模块应该暴露错误优先的回调接口，且应该经常在回调中检查错误。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">readJSON</span>(<span class="params">filePath, callback</span>) </span>&#123;</span><br><span class="line">    fs.readFile(filePath, <span class="function"><span class="keyword">function</span>(<span class="params">err, data</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> parsedJson;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//处理错误，我们总是要在回调函数中处理错误，这是不可或缺的。</span></span><br><span class="line">        <span class="keyword">if</span>(err) &#123;</span><br><span class="line">            <span class="keyword">return</span> callback(err);</span><br><span class="line">            <span class="comment">//当发生错误时，使用return，让程序在if语句中停止执行</span></span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//解析JSON</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//使用try-catch来捕获同步函数中的异常</span></span><br><span class="line">            parsedJson = <span class="built_in">JSON</span>.parse(data);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (exception) &#123;</span><br><span class="line">            <span class="keyword">return</span> callback(exception);</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">//只有一切正常时，则直接调用回调函数</span></span><br><span class="line">        <span class="keyword">return</span> callback(<span class="literal">null</span>, parsedJson);</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="事件发射"><a href="#事件发射" class="headerlink" title="事件发射"></a>事件发射</h3><p>事件模块是Node.js内置的对观察者模式的实现，通过EventEmitter属性提供一个构造函数，该构造函数的实例中有两个常用方法，其中on方法用来监听指定事件，并触发回调函数；emit方法用于发布事件。</p>
<p>events模块只提供了一个对象：events.EventEmitter；其核心部分就是对事件触发与实践监听器功能的封装。</p>
<p>默认时，Node.js允许同一个实践最多指定10个回调函数，超过时会发出警告，需要用setMaxListeners方法修改最大监听数。</p>
<p>1、事件操作：events模块默认支持以下：newListener事件与removeListener事件，分别在新增、删除回调函数时触发。</p>
<p>2、once方法，与on类似，但回调函数只会被触发一次。</p>
<p>3、获取监听器信息：通过listeners方法实现，该方法接收一个事件名称作为参数，返回由该事件所有回调函数组成的数组。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> EventEmitter = <span class="built_in">require</span>(<span class="string">'events'</span>);</span><br><span class="line"><span class="keyword">const</span> emitter = <span class="keyword">new</span> EventEmitter();</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">onlyOnce</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(ee.listeners(<span class="string">"firstConnection"</span>));</span><br><span class="line">    ee.removeListener(<span class="string">"firstConnection"</span>, onlyOnce);</span><br><span class="line">    <span class="built_in">console</span>.log(ee.listeners(<span class="string">"firstConnection"</span>));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">ee.on(<span class="string">"firstConnection"</span>, onlyOnce);</span><br><span class="line">ee.emit(<span class="string">"firstConnection"</span>);</span><br><span class="line">ee.emit(<span class="string">"firstConnection"</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//以上代码显示两次由回调函数组成的数组，第一次只有onlyOnce，第二次由于已经remove，因此为空数组</span></span><br><span class="line"><span class="comment">//[[Function: onlyOnce]]</span></span><br><span class="line"><span class="comment">//[]</span></span><br></pre></td></tr></table></figure>

<p>4、事件错误处理：方式与常规函数一致，即在回调函数里判断error参数，同时结合Event中最常用的on方法来监听error事件。</p>
<p>为什么http.get方法可以用on来进行错误处理呢：</p>
<p>http.get方法本质是用request来请求的 =》request方法是ClientRequet中实现的，基于Stream =》 而Stream是继承字events的。</p>
<p>http模块是Node的核心模块，Stream是核心中核心，而events就是底层基础，为核心代码服务。</p>
<p>回调与事件的选择问题：</p>
<p>API推荐优先使用错误优先的回调方式，统一使用SDK的回调风格；在同一对象内，则用EventEmitter解耦。可以合理地利用集成。</p>
<h1 id="回调处理"><a href="#回调处理" class="headerlink" title="回调处理"></a>回调处理</h1><h2 id="Thunk"><a href="#Thunk" class="headerlink" title="Thunk"></a>Thunk</h2><p>Thunk函数：JS采用的是“传名调用”的参数求职策略，即将参数放在一个临时函数之中，再将这个临时函数传入函数体，这个临时函数就叫做Thunk函数。在co应用中，为编写与同步一样的异步代码，比较常见的就是用Thunk函数。</p>
<h2 id="Promise"><a href="#Promise" class="headerlink" title="Promise"></a>Promise</h2><h2 id="Generator"><a href="#Generator" class="headerlink" title="Generator"></a>Generator</h2><p>Generator函数和Generator是ES6新加入的特性，其本质上是一种特殊的迭代器。Generator本意是Iterator生成器，函数运行到yield时退出，并保留上下文，在下次进入时可以继续运行。生成器函数也是一种哈桑农户，语法上仅比普通函数多了一个“*”，其函数体内部可以使用yield和yield关键字。</p>
<p>由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态，其yield语句就是暂停标志。遍历器对象的next方法运行逻辑如下：</p>
<p>1、遇到yield语句就暂停执行后面的操作，并将紧跟在yield后的表达式值作为返回的对象的value属性值。</p>
<p>2、下一次调用next方法时，再继续往下执行，直到遇到下一条yield语句。</p>
<p>3、如果没有再遇到新得yield语句，就一直运行到函数结束，直到return语句位置，并将return语句后的表达式值作为返回对象的value属性值。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//实例代码如下，Generator是生成器，需要先生成对象才能继续执行。</span></span><br><span class="line"><span class="function"><span class="keyword">function</span>* <span class="title">doSomething</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'1'</span>);</span><br><span class="line">    <span class="keyword">yield</span> <span class="string">'1'</span>;<span class="comment">//Line(A);</span></span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">'2'</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> gen = doSomething();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> a = gen1.next();</span><br><span class="line"><span class="comment">//a为&#123;value:'1',done:false&#125;;打印出1，然后代码悬停至Line(A)处</span></span><br><span class="line"><span class="keyword">let</span> b = gen1.next();</span><br><span class="line"><span class="comment">//b为&#123;value:'',done:true&#125;;done为true时，代表generator里的异步操作均执行完；会打印出2.</span></span><br></pre></td></tr></table></figure>

<p>co模块是为了能够在generator中使用多个yield，co是ES6generator的执行其，但不能与async函数混用，返回的结果是Promise对象。</p>
<p>co适用于调用generator的场景。在async函数中，await关键字后可以接Promise，co的返回值是Promise。</p>
<p>co的典型用法是作为generator的执行器使用：1、co是generator执行器，因此只要管好yield即可，不用关心generator是怎么执行的；2、co的返回值是Promise，后面可以直接接Promise的then函数。</p>
<p>co的简单源码如下：</p>
<h2 id="async函数"><a href="#async函数" class="headerlink" title="async函数"></a>async函数</h2><p>generator的弊病是没有执行器，因此要借助co。但一直借助外部模块不是办法，因此，async/await就被创造出来了。async函数是什么？其实它的本质就是Generator函数的语法糖。</p>
<p>async函数返回一个promise对象，可以使用then方法添加回调函数，当函数执行的时候，一旦遇到await就会先返回，等到异步操作完成，再接着执行函数体内后面的语句。同样，async函数内部return语句返回的值，会成为then方法回调函数的参数。同样，async函数内部抛出错误会导致返回的promise对象变为reject状态，该错误会被catch方法回调函数接收到。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">getStockPriceByName</span>(<span class="params">name</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> symbol = <span class="keyword">await</span> getStockSymbol(name);</span><br><span class="line">    <span class="keyword">var</span> stockPrice = <span class="keyword">await</span> getStockPrice(symbol);</span><br><span class="line">    <span class="keyword">return</span> stockPrice;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">getStockPriceByName(<span class="string">'goog'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">res</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(res);</span><br><span class="line">&#125;);</span><br><span class="line"><span class="comment">//调用该函数会在查询完之后，打印查询结果，同时不影响同步进程。</span></span><br></pre></td></tr></table></figure>

<p>async函数返回的Promise对象必须等到内部所有await命令后面的promise对象执行完才会发生状态改变，除非遇到return语句或者抛出错误。也就是说，只有async函数内部的异步操作执行完，才会执行then方法指定的回调函数。</p>
<p>async的执行过程示例代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//async的执行过程</span></span><br><span class="line">exports.list = <span class="keyword">async</span> (ctx, next) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> students = <span class="keyword">await</span> Student.getAllAsync();</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">await</span> ctx.render(<span class="string">'students/index'</span>, &#123;</span><br><span class="line">            students : students</span><br><span class="line">        &#125;)</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">        <span class="keyword">return</span> ctx.api_error(err);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">以上代码完成了三个操作：</span></span><br><span class="line"><span class="comment">1、通过await Student.getAllAsync()，获取所有的student信息</span></span><br><span class="line"><span class="comment">2、通过await ctx.render渲染页面</span></span><br><span class="line"><span class="comment">3、由于是同步代码，用try,catch进行异常处理。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>正常情况下，await命令后是一个Promise对象，如果不是，会被转成一个立即resolve的Promise对象。</p>
<p>错误处理：如果await后面的异步操作出错，那么等同于async函数返回的Promise对象被reject。而防止出错的方法是：将其放在try…catch代码块中，如果有多个await命令，则可以统一放在try…catch结构中。</p>
<p>await的三种用法：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//await+async函数的实例代码如下</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//await+Promise用法如下：</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//await+co+Generator用法如下：</span></span><br></pre></td></tr></table></figure>

<p>上面三种用法的要点如下：1、co的返回值是Promise，所以await可以和co直接连接使用；2、co的参数是Generator；3、在Generator里可以使用yield。</p>
<p>async函数的要点如下：1、async函数本身具有执行能力，不用像generator一样借助co；2、async进行异常处理时，可以用try-catch或者Promise的处理方式；3、async后接Promise，Promise自身可应对所有流程，并可用all和race来补齐并行处理机制；4、await+promise可以覆盖所有应用场景。</p>
<h3 id="异常处理"><a href="#异常处理" class="headerlink" title="异常处理"></a>异常处理</h3><p>对于async函数来说，其await语句是同步执行的，所以最正常的流程处理需要采用try-catch，这与generator函数一样。</p>
<p>在常见的WEB应用中，总结的实践经验是：DAO层用Promise比较好，而Service层用async函数更好。DAO层使用Promise基本只需要几层代码，一般单一模型的操作不会太复杂，而Service层则一般针对多模型进行操作，可拆分成多个简单操作，然后用await进行组合。</p>
<h3 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h3><p>async函数的原理就是将generator函数和自动执行器包装在一个函数中。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">args</span>) </span>&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//等同于</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">fn</span>(<span class="params">args</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> spawn(<span class="function"><span class="keyword">function</span>* (<span class="params"></span>) </span>&#123;</span><br><span class="line">        <span class="comment">//...</span></span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//其中的spawn函数就是自动执行器，下面写出它的简单实现。</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">spawn</span>(<span class="params">genF</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span> (<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">var</span> gen = genF();</span><br><span class="line">        <span class="function"><span class="keyword">function</span> <span class="title">step</span> (<span class="params">nextF</span>) </span>&#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="keyword">var</span> next = nextF();</span><br><span class="line">            &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">                <span class="keyword">return</span> reject(e);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (next.done) &#123;</span><br><span class="line">                <span class="keyword">return</span> resolve(next.value);</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="built_in">Promise</span>.resolve(next.value).then(<span class="function"><span class="keyword">function</span>(<span class="params">v</span>) </span>&#123;</span><br><span class="line">                step(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">                    <span class="keyword">return</span> gen.next(v);</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>) </span>&#123;</span><br><span class="line">               <span class="keyword">return</span> gen.throw(e); </span><br><span class="line">            &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">        step(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">            <span class="keyword">return</span> gen.next(<span class="literal">undefined</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>至此，狼书第一本总结完毕，开始看狼书第二本。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/14/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/elementUI%E4%BD%BF%E7%94%A8%E9%81%BF%E5%9D%91/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/14/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/elementUI%E4%BD%BF%E7%94%A8%E9%81%BF%E5%9D%91/" class="post-title-link" itemprop="url">elementUI使用避坑</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-14 21:28:16" itemprop="dateCreated datePublished" datetime="2021-10-14T21:28:16+08:00">2021-10-14</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:31:27" itemprop="dateModified" datetime="2021-11-03T16:31:27+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%AD%A6%E6%A0%A1%E9%A1%B9%E7%9B%AE%E5%BA%94%E7%94%A8/" itemprop="url" rel="index"><span itemprop="name">学校项目应用</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>之前项目是用elementUI做的，遇到了一些坑的地方，不过好多都已经忘了，这里想简单做个记录吧，以后再遇到问题会容易回想。</p>
<p>避坑的第一条：<strong>先看清楚版本号，新旧版本不兼容导致的Bug很常见。</strong></p>
<h1 id="初级问题"><a href="#初级问题" class="headerlink" title="初级问题"></a>初级问题</h1><h2 id="el-tab与echarts"><a href="#el-tab与echarts" class="headerlink" title="el-tab与echarts"></a>el-tab与echarts</h2><p>今天写项目的时候遇到一个问题，就是使用element的tab-pane进行切换的时候，切换到第二个tab的时候echarts图表的宽高始终只有100px，怎么找都没有找到是什么问题导致的，最后查阅资料发现，该组件不会刷新生命周期钩子。也就是说不会触发echarts组建中的销毁初始化等，这几个tab中的echarts是同时加载的。</p>
<p>所以，解决方法是在渲染echarts图表那里加上v-if</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">div</span> <span class="attr">v-if</span>=<span class="string">"activeName === 'first'"</span> <span class="attr">class</span>=<span class="string">"chartsBox"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">'saleTrendCharts'</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>然后在mounted()这里先加载一个tab的图表</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">mounted</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.setSaleTrendCharts();</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure>

<p>然后在通过watch监听切换tab的值，再通过$nextTick之后渲染图表就可以了。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">watch</span>: </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">activeName</span>(<span class="params">val</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (val === <span class="string">'first'</span>) &#123;</span><br><span class="line">            <span class="keyword">this</span>.$nextTick(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">                <span class="keyword">this</span>.setSaleTrendCharts();</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">this</span>.$nextTick(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">                <span class="keyword">this</span>.setSaleFxCharts();</span><br><span class="line">            &#125;)</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/14/node/node.js%E6%A8%A1%E5%9D%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/14/node/node.js%E6%A8%A1%E5%9D%97/" class="post-title-link" itemprop="url">nodejs模块</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-14 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-14T12:18:39+08:00">2021-10-14</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-03 16:20:25" itemprop="dateModified" datetime="2021-11-03T16:20:25+08:00">2021-11-03</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/node/" itemprop="url" rel="index"><span itemprop="name">node</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>Node.js是基于事件驱动的；当我们的服务器监听请求时，请求其实任意时刻都可以到达。对于多线程的语言，任何时候当有请求进入的时候，服务器就为这一请求新建一个进程。这就是是多线程的使用。</p>
<p>但Node.js为单线程，因此不能这样处理，这里我们定义一个处理请求的函数，就是回调函数，只有在接收请求时才会调用这个回调，这就是Node的异步。</p>
<p>在Node中,我们定义回调函数被触发时，有两个参数被传入：request和response。我们可以通过它来处理HTTP请求的细节，并且响应请求。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">"http"</span>);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">onRequest</span>(<span class="params">request, response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Request received."</span>);</span><br><span class="line">  response.writeHead(<span class="number">200</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">  response.write(<span class="string">"Hello World"</span>);</span><br><span class="line">  response.end();</span><br><span class="line">&#125;</span><br><span class="line">http.createServer(onRequest).listen(<span class="number">8888</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"Server has started."</span>);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">当收到请求时，使用 response.writeHead() 函数发送一个HTTP状态200和HTTP头的内容类型（content-type），使用 response.write() 函数在HTTP相应主体中发送文本“Hello World"。</span></span><br><span class="line"><span class="comment">最后，我们调用 response.end() 完成响应。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>在C++或C#中，当我们谈到对象，指的是类或者结构体的实例。对象根据他们实例化的模板（就是所谓的类），会拥有不同的属性和方法。但在JavaScript里对象不是这个概念。在JavaScript中，对象就是一个键/值对的集合 – 你可以把JavaScript的对象想象成一个键为字符串类型的字典。但如果JavaScript的对象仅仅是键/值对的集合，它又怎么会拥有方法呢？好吧，这里的值可以是字符串、数字或者……函数！</p>
<p><strong>在node中除了代码，所有的一切都是并行执行的。</strong>Node.js可以在不新增额外线程的情况下，依然可以对任务进行并行处理。它通过事件event loop机制来实现并行操作，同时，使用回调。</p>
<h1 id="简单实践"><a href="#简单实践" class="headerlink" title="简单实践"></a>简单实践</h1><p>正常情况下，应用已经可以通过应用各层之间传递值的方式（请求处理程序 -&gt; 请求路由 -&gt; 服务器）将请求处理程序返回的内容（请求处理程序最终要显示给用户的内容）传递给HTTP服务器。</p>
<p>现在我们采用如下这种新的实现方式：相对采用将内容传递给服务器的方式，我们这次采用将服务器“传递”给内容的方式。 从实践角度来说，就是将<em>response</em>对象（从服务器的回调函数*onRequest()*获取）通过请求路由传递给请求处理程序。 随后，处理程序就可以采用该对象上的函数来对请求作出响应。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//server.js</span></span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">"http"</span>);</span><br><span class="line"><span class="keyword">var</span> url = <span class="built_in">require</span>(<span class="string">"url"</span>);</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">start</span>(<span class="params">route, handle</span>) </span>&#123;</span><br><span class="line">  <span class="function"><span class="keyword">function</span> <span class="title">onRequest</span>(<span class="params">request, response</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> pathname = url.parse(request.url).pathname;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"Request for "</span> + pathname + <span class="string">" received."</span>);</span><br><span class="line"></span><br><span class="line">    route(handle, pathname, response);</span><br><span class="line">  &#125;</span><br><span class="line">  http.createServer(onRequest).listen(<span class="number">8888</span>);</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Server has started."</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">exports.start = start;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">相对此前从route()函数获取返回值的做法，这次我们将response对象作为第三个参数传递给route()函数，并且，我们将onRequest()处理程序中所有有关response的函数调都移除，因为我们希望这部分工作让route()函数来完成。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">下面就来看看我们的router.js:</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">route</span>(<span class="params">handle, pathname, response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"About to route a request for "</span> + pathname);</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> handle[pathname] === <span class="string">'function'</span>) &#123;</span><br><span class="line">    handle[pathname](response);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"No request handler found for "</span> + pathname);</span><br><span class="line">    response.writeHead(<span class="number">404</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">    response.write(<span class="string">"404 Not found"</span>);</span><br><span class="line">    response.end();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">exports.route = route;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">同样的模式：相对此前从请求处理程序中获取返回值，这次取而代之的是直接传递response对象。</span></span><br><span class="line"><span class="comment">如果没有对应的请求处理器处理，我们就直接返回“404”错误。</span></span><br><span class="line"><span class="comment">最后，我们将requestHandler.js修改为如下形式</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> exec = <span class="built_in">require</span>(<span class="string">"child_process"</span>).exec;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">start</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Request handler 'start' was called."</span>);</span><br><span class="line"></span><br><span class="line">  exec(<span class="string">"ls -lah"</span>, <span class="function"><span class="keyword">function</span> (<span class="params">error, stdout, stderr</span>) </span>&#123;</span><br><span class="line">    response.writeHead(<span class="number">200</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">    response.write(stdout);</span><br><span class="line">    response.end();</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">upload</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Request handler 'upload' was called."</span>);</span><br><span class="line">  response.writeHead(<span class="number">200</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">  response.write(<span class="string">"Hello Upload"</span>);</span><br><span class="line">  response.end();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">exports.start = start;</span><br><span class="line">exports.upload = upload;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">我们的处理程序函数需要接收response参数，为了对请求作出直接的响应。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">start处理程序在exec()的匿名回调函数中做请求响应的操作，而upload处理程序仍然是简单的回复“Hello World”，只是这次是使用response对象而已。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">这时再次我们启动应用（node index.js），一切都会工作的很好。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">如果想要证明/start处理程序中耗时的操作不会阻塞对/upload请求作出立即响应的话，可以将requestHandlers.js修改为如下形式：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">var</span> exec = <span class="built_in">require</span>(<span class="string">"child_process"</span>).exec;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">start</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Request handler 'start' was called."</span>);</span><br><span class="line"></span><br><span class="line">  exec(<span class="string">"find /"</span>,</span><br><span class="line">    &#123; <span class="attr">timeout</span>: <span class="number">10000</span>, <span class="attr">maxBuffer</span>: <span class="number">20000</span>*<span class="number">1024</span> &#125;,</span><br><span class="line">    <span class="function"><span class="keyword">function</span> (<span class="params">error, stdout, stderr</span>) </span>&#123;</span><br><span class="line">      response.writeHead(<span class="number">200</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">      response.write(stdout);</span><br><span class="line">      response.end();</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">upload</span>(<span class="params">response</span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"Request handler 'upload' was called."</span>);</span><br><span class="line">  response.writeHead(<span class="number">200</span>, &#123;<span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span>&#125;);</span><br><span class="line">  response.write(<span class="string">"Hello Upload"</span>);</span><br><span class="line">  response.end();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">exports.start = start;</span><br><span class="line">exports.upload = upload;</span><br></pre></td></tr></table></figure>

<h1 id="模块规范"><a href="#模块规范" class="headerlink" title="模块规范"></a>模块规范</h1><h2 id="CommonJS规范"><a href="#CommonJS规范" class="headerlink" title="CommonJS规范"></a>CommonJS规范</h2><p>CommonJS是一个规范，通过简单的API声明服务器的模块，目标是让JS可以运行在浏览器意外的所有地方，</p>
<p>Modules/1.0规范：</p>
<p>1、定义全局函数require，通过传入模块标识来引入其他模块，执行的结果即其他模块暴露出的API；2、如果被require函数引入的模块也包含依赖，则依次加载这些依赖；3、如果引入模块失败，则require函数应该抛出异常；4、模块通过变量exports来向外暴露API，exports只能是一个对象，暴露的API须作为此对象的属性。</p>
<p>之前的规范主要源于服务端，为浏览器端同样实现模块化，需对规范进行升级，基本分为两个派别：</p>
<p>1、Webpack等使用的Browserify工具。</p>
<p>2、async派：不属于CommonJS的范畴，独立制定浏览器端的JS模块化规范AMD。代表项目有RequireJS和SeaJS。</p>
<p>Node.js与CommonJS在模块系统主要通过两个关键字进行交互，即require、exports。在Node.js的实现中，模块的名字在node_modules目录下，如果不在，就会去查找指定路径。而exports是一个特殊的对象，其任何输出都会作为一个对外暴露的公共API。</p>
<p>Node.js与CommonJS的区别：在Node.js中。module.exports是真正特殊对象，真正的对外暴露接口，而exports只是一个变量，是被默认的module.exports绑定的。而CommonJS规范中没有module.exports对象。</p>
<h3 id="模块导出、引入"><a href="#模块导出、引入" class="headerlink" title="模块导出、引入"></a>模块导出、引入</h3><p>使用module.exports实现模块导出，多用于编写对外暴露统一对象API的代码；使用exports多用于编写对外暴露多个API的工具类代码。</p>
<p>其实module.exports才是真正的接口，exports只不过是它的一个辅助工具，最终将结果返回给调用方的是module.exports，而不是exports。module.exports只暴露了一个函数。，当两者共存时，module.exports优先级更高。</p>
<p>使用require来进行模块引入：要点：1、require是一个函数，参数为该模块的标识；2、require函数的返回值是module.exports对象；3、require引入的核心是模块标识，而模块标识有两种，一种是npm模块，安装于node_modules目录下；一种是自定义的本地模块，则带有相对路径。</p>
<h3 id="模块循环引用"><a href="#模块循环引用" class="headerlink" title="模块循环引用"></a>模块循环引用</h3><p>当两个不同对象各有一个强引用指向对方时，循环引用便产生了，称为module cycles。</p>
<p>在这种情况下require返回的对象必须至少包含此外部模块在调用require函数之前就已经准备完毕的输出。例如：b.js需要引用a.js，这里b.js作为当前模块，a.js就是外部模块，那么a.js应该在引用b,js之前（即进入当前模块执行环境）就返回输出。</p>
<h2 id="执行原理"><a href="#执行原理" class="headerlink" title="执行原理"></a>执行原理</h2><p>代码的每个模块在被引用时都被头尾包装，代码执行后将exports对象返回给引用方。exports其实就是通过参数传递过来的，如果exports的引用被修改，那么返回的将是原来的对象，这时需要用module.exports来代替。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"hello"</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//上面的模块代码会被包裹到函数，类似于</span></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params">exports, require, module, _filename, _dirname</span>) </span>&#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"hello"</span>);</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//代码包裹的实践如下：</span></span><br><span class="line">NativeModule.wrapper = &#123;</span><br><span class="line">    <span class="string">'(function(exports, require, module, _filename, _dirname) &#123;'</span>, <span class="string">'\n&#125;);'</span></span><br><span class="line">&#125;;</span><br><span class="line">NativeModule.wrap = <span class="function"><span class="keyword">function</span>(<span class="params">script</span>)</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> NativeModule.wrapper[<span class="number">0</span>] + script + NativeModule.wrapper[<span class="number">1</span>];</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">//具体的编译源码如下：</span></span><br><span class="line">NativeModule.prototype.compile = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> source = NativeModule.getSource(<span class="keyword">this</span>.id);</span><br><span class="line">    source = NativeModule.wrap(source);</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">this</span>.loading = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">var</span> fn = runInThisContext(source, &#123;</span><br><span class="line">           filename : <span class="keyword">this</span>.filename,</span><br><span class="line">           lineOffset : <span class="number">0</span>,</span><br><span class="line">           displayErrors : <span class="literal">true</span></span><br><span class="line">        &#125;);</span><br><span class="line">        fn(<span class="keyword">this</span>.exports, NativeModule.require, <span class="keyword">this</span>, <span class="keyword">this</span>.filename);</span><br><span class="line">        <span class="keyword">this</span>.loaded = <span class="literal">true</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">this</span>.loading = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//具体的执行代码其实是：runInThisContext函数。</span></span><br></pre></td></tr></table></figure>

<h3 id="模块编译"><a href="#模块编译" class="headerlink" title="模块编译"></a>模块编译</h3><p>Node.js模块化编译的操作：</p>
<p>1、保证顶层变量（通过let、const、var定义），只在模块内起作用；</p>
<p>2、帮助提供一些全局查找的变量，实际上是指定给模块的变量，具体如：module.exports对象可用于从模块上对外暴露值；注入filename、dirname，即模块的文件名和路径，这样我们可以在任意文件中使用。</p>
<p> 一般在执行Node.js时，命令通常是node xxx.js。在lib/internal/bootstrap_node.js里可以找到模块执行的核心代码逻辑。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//保证process.argv[1]是完整路径</span></span><br><span class="line"><span class="keyword">const</span> path = NativeModule.require(<span class="string">'path'</span>);</span><br><span class="line">process.argv[<span class="number">1</span>] = path.resolve(process.argv[<span class="number">1</span>]);</span><br><span class="line"></span><br><span class="line"><span class="comment">//通过require引入lib/modules.js，然后赋值给Module。</span></span><br><span class="line"><span class="keyword">const</span> Module = NativeModule.require(<span class="string">'module'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//检查用户输入的-c或-check参数</span></span><br><span class="line"><span class="keyword">if</span> (process._syntax_check_only != <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> fs = NativeModule.require(<span class="string">'fs'</span>);</span><br><span class="line">    <span class="comment">//读取源码</span></span><br><span class="line">    <span class="keyword">const</span> filename = Module._resolveFilename(process.argv[<span class="number">1</span>]);</span><br><span class="line">    <span class="keyword">var</span> source = fs.readFileSync(filename, <span class="string">'utf-8'</span>);</span><br><span class="line">    checkScriptSyntax(source, filename);</span><br><span class="line">    process.exit(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//预加载模块preloadModules()</span></span><br><span class="line">preloadModules();</span><br><span class="line"><span class="comment">//执行模块Module.runMain()</span></span><br><span class="line">Module.runMain();</span><br></pre></td></tr></table></figure>

<p>以上代码的核心是Module.runMain()，其具体的实现代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">Module.runMain = <span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="comment">//加载主模块</span></span><br><span class="line">    Module._load(process.argv[<span class="number">1</span>], <span class="literal">null</span>, <span class="literal">true</span>);</span><br><span class="line">    <span class="comment">//先执行nextTickQueue中的回调函数</span></span><br><span class="line">    process._tickCallback();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//核心的Module._load方法的代码如下：</span></span><br><span class="line">Main._load = <span class="function"><span class="keyword">function</span> (<span class="params">request, parent, isMain</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (parent) &#123;</span><br><span class="line">        ...</span><br><span class="line">    &#125;</span><br><span class="line">    tryModuleLoad(<span class="built_in">module</span>, filename);</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">module</span>.exports;</span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">        其实以上代码的要点在于对缓存和模块类型的处理：</span></span><br><span class="line"><span class="comment">        1、如果模块已经存在于缓存中，则返回cachedModule.exports对象</span></span><br><span class="line"><span class="comment">        2、如果模块是native模块，则调用NativeModule.require(),参数为文件名，最后返回结果。</span></span><br><span class="line"><span class="comment">        3、其他情况下会为该文件创建一个新得模块，并将其保存到缓存中。随后，加载文件内容，然后返回给module.exports对象。</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">	对于我们自己编写的代码，一般会是最后这种情况，truModuleLoad的源码如下：</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">tryModuleLoad</span>(<span class="params">module, filename</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> threw = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="built_in">module</span>.load(filename);</span><br><span class="line">        threw = <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (threw) &#123;</span><br><span class="line">            <span class="keyword">delete</span> Module_cache[filename];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//上面示例的核心是module.load部分，代码如下：</span></span><br><span class="line">Module.prototype.load = <span class="function"><span class="keyword">function</span> (<span class="params">filename</span>) </span>&#123;</span><br><span class="line">    ...</span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">/*这里的文件后缀有三种</span></span><br><span class="line"><span class="comment">1、.js是通过module._compile来进行编译处理的，通过fs模块同步读取文件后编译执行；</span></span><br><span class="line"><span class="comment">2、.json通过JSON.parse将结果直接返回module.exports，通过fs模块同步读取文件后编译执行</span></span><br><span class="line"><span class="comment">3、.node是用c++编写的扩展文件，通过process.dlopen方法加载最后编译生成得文件，并返回结果。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<h2 id="模块引用"><a href="#模块引用" class="headerlink" title="模块引用"></a>模块引用</h2><p>Node中的模块有两种类型：核心模块和文件模块，核心模块直接用名称获取。</p>
<p>require方法接收以下几种类型的参数传递：1、http、fs、path等内置模块；2、./mod等相对路径文件模块；3、/pathtomodule等绝对路径文件模块；4、mod:非原生模块的文件模块。</p>
<p>require实现的源码位于lib/internal/module.js，具体如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">makeRequireFunction</span>(<span class="params">mod</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> Module = mod.constructor;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">require</span>(<span class="params">path</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            exports.requireDepth += <span class="number">1</span>;</span><br><span class="line">            <span class="keyword">return</span> mod.require(path);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            exports.requireDepth -= <span class="number">1</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">resolve</span>(<span class="params">request</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> Module._resolveFilename(request, mod);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">require</span>.resolve = resolve;</span><br><span class="line">    <span class="built_in">require</span>.main = process.mainModule;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//支持外部扩展类型</span></span><br><span class="line">    <span class="built_in">require</span>.extensions = Module._extensions;</span><br><span class="line">    <span class="built_in">require</span>.cache = Module._cache;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">require</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>makeRequireFunction是在lib/module.js里的compile方法中调用的，require其实是mod.require的别名，而mod其实是NativeModule的实例，所以，require是NativeModule.require的别名。</p>
<h2 id="全局对象"><a href="#全局对象" class="headerlink" title="全局对象"></a>全局对象</h2><p>模块有两种写法，一种是基于CommonJS规范编写的，第二种写法则是全局对象写法。</p>
<h3 id="内置对象"><a href="#内置对象" class="headerlink" title="内置对象"></a>内置对象</h3><p>内置对象是指在所有Node.js模块中，无须安装就可以使用的模块。可以分为以下几类：</p>
<p>1、为模块包装而提供的全局对象：例如，exports、require、module、filename、dirname。</p>
<p>2、内置的process对象，process作为核心模块，它可以对Node.js的各种信息进行绑定。</p>
<p>3、控制台Console模块。</p>
<p>4、EventLoop相关API的实现。</p>
<h3 id="Buffer"><a href="#Buffer" class="headerlink" title="Buffer"></a>Buffer</h3><p>Java中Buffer用于和NIO通道进行交互，其核心是利用缓存机制将内存块包装成NIO Buffer对象进行网络传输，并提供访问该内存块的一组方法。Node也面临一样的问题，虽然很好处理Unicode编码的字符串，但需要处理二进制数据。</p>
<p>Buffer代表一个缓冲区，用于存储二进制数据，俗称字节流，是I/O传输时常用的处理方式。相比于字符串，Buffer可以免去编码和解码的过程，节省CPU成本。Buffer也是Stream的基础。</p>
<h3 id="global"><a href="#global" class="headerlink" title="global"></a>global</h3><p>Global对象跟JS中的普通对象是一样的，主要用于扩展变量和方法。</p>
<h1 id="ES模块"><a href="#ES模块" class="headerlink" title="ES模块"></a>ES模块</h1><p>ES模块的目标是创建一个同时兼容CommonJS和AMD的格式，通过编译时加载，在编译时就能确定模块间的依赖关系，比CommonJS模块的加载效率更高。而在异步加载和配置模块加载方面，则借鉴AMD规范，提升执行效率。</p>
<p>ES6模块加载与CommonJS的差异主要以下两点：</p>
<p>1、CommonJS模块输出的是一个值的复制，ES6模块输出的是值得引用；</p>
<p>2、CommonJS模块是运行时加载，ES6模块时编译时输出接口。</p>
<h2 id="导入导出"><a href="#导入导出" class="headerlink" title="导入导出"></a>导入导出</h2><p>模块带入有两种方式，分为直接导入和按需导入。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> fs <span class="keyword">from</span> <span class="string">'fs'</span>;</span><br><span class="line"><span class="comment">//上面为直接导入，等价于CommonJS中的下面代码</span></span><br><span class="line"><span class="keyword">const</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>);</span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">CommonJS的加载实质上是整体加载，然后在运行是读取其方法，为运行时加载，导致完全无法在编译时进行静态优化。</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; readFile &#125; <span class="keyword">from</span> <span class="string">'fs'</span>;</span><br><span class="line"><span class="comment">/*以上代码的是指是从fs模块中架子readFile方法，该加载为编译时加载或静态加载</span></span><br><span class="line"><span class="comment">即ES6可以在编译时完成模块加载，效率更高。</span></span><br><span class="line"><span class="comment">但这也导致ES6模块本身无法被引用，因为它不是对象。</span></span><br><span class="line"><span class="comment">由于ES6模块是编译时加载，使得静态分析成为可能。</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>导出时，exports命令规定的是对外的接口，必须与模块内部的变量建立一一对应的关系。另外，exports语句输出的接口与其对应的值是动态绑定关系，即通过该接口可以取到模块内部实时的值。这一点与CommonJS规范完全不同，CommonJS模块输出的是值得缓存，不存在动态更新。</p>
<p>同样，由于import命令是编译阶段执行得，在代码运行之前，由于import是静态执行，所以不能使用表达式与变量，获取其他只有运行时才能得到结果得语句。</p>
<h2 id="import"><a href="#import" class="headerlink" title="import()"></a>import()</h2><p>import命令会被JS静态分析，先于模块内得其他模块执行。这样得设计固然有利于编译器提高效率，但也导致无法在运行时加载模块，在语法上使得条件加载不可能实现。</p>
<p>这里，ES6引入了import函数来完成动态加载，import(specifier)，其参数specifier指定要加载得模块的位置。</p>
<p>import()返回以恶搞Promise对象，该函数可以用在任何地方，非模块的脚本也可以使用。import()类似于Node的require方法，区别主要是：前者是异步加载，后者是同步加载。</p>
<p>应用场景：1、按需加载；2、条件加载；3、动态模块路径生成。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  

      
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://example.com/2021/10/10/react/react%E5%B7%A5%E7%A8%8B%E5%B8%88%E4%BF%AE%E7%82%BC%E6%8C%87%E5%8D%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/author.jpg">
      <meta itemprop="name" content="Li Yudong">
      <meta itemprop="description" content="">
    </span>
    
    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Technological Blog">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          
            <a href="/2021/10/10/react/react%E5%B7%A5%E7%A8%8B%E5%B8%88%E4%BF%AE%E7%82%BC%E6%8C%87%E5%8D%97/" class="post-title-link" itemprop="url">react工程师修炼指南（一）</a>
        </h2>
    
        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>
    
              <time title="创建时间：2021-10-10 12:18:39" itemprop="dateCreated datePublished" datetime="2021-10-10T12:18:39+08:00">2021-10-10</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2021-11-10 16:39:41" itemprop="dateModified" datetime="2021-11-10T16:39:41+08:00">2021-11-10</time>
              </span>
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-folder"></i>
              </span>
              <span class="post-meta-item-text">分类于</span>
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/react/" itemprop="url" rel="index"><span itemprop="name">react</span></a>
                </span>
            </span>
    
          
    
        </div>
      </header>
    
    
    
    
    <div class="post-body" itemprop="articleBody">
    
      
          <h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p> React是专注于视图层的解决方案，特点为组件化编程与声明式开发。</p>
<p>声明式与命令式区别：命令式注重过程，开发者需要告诉程序每步要怎么做；声明式注重结果，直接告诉程序要什么。</p>
<h1 id="React基础"><a href="#React基础" class="headerlink" title="React基础"></a>React基础</h1><h2 id="ReactDOM"><a href="#ReactDOM" class="headerlink" title="ReactDOM"></a>ReactDOM</h2><p>在React中需要跟真实的DOM打交道都需要通过ReactDOM的API来进行。</p>
<p>但除了render以外，不建议在项目中使用其他API。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*1、render方法是ReactDOM在开发时唯一常用的API，用于将React生成的虚拟DOM生成到虚拟DOM中。</span></span><br><span class="line"><span class="comment">element是React生成的虚拟DOM；container要放置在element的容器中，它必须是一个已经存在的真实DOM节点；</span></span><br><span class="line"><span class="comment">callback是将ReactNode渲染到container之后的回调。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ReactDOM.render(element, container[, callback]);</span><br><span class="line"></span><br><span class="line">ReactDOM.render(<span class="string">"Hello React"</span>, <span class="built_in">document</span>.querySelector(<span class="string">"#root"</span>), () =&gt; &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"渲染完成"</span>)</span><br><span class="line">&#125;)</span><br><span class="line"><span class="comment">/*这里将“Hello React”这段字符串渲染到了#root这个div;render常用于渲染整个项目的跟组件，使用render时</span></span><br><span class="line"><span class="comment">要注意container中如果有其他内容，就都会被替换掉。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/*2、hydrate方法一般配合SSR使用，其作用于ReactDOM复用ReactDOMServer内容时，能够尽可能保留其结构，并补充事件绑定等特性，</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ReactDOM.hydrate(element, container[, callback])</span><br><span class="line"></span><br><span class="line"><span class="comment">/*3、Component指的是React组件，若其已经渲染到DOM，可通过findDOMNode来获取真实DOM。但其实一般不建议，要获取真实DOM请使用ref</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ReactDOM.findDOMNode(Component)</span><br><span class="line"></span><br><span class="line"><span class="comment">/*4、container类似于render中的container，一个真实DOM节点。unmountComponentAtNode方法用于container中删除已安装的React组件，并清理其事件处理程序和状态</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ReactDOM.unmountComponentAtNode(<span class="built_in">document</span>.querySelector(<span class="string">"#root"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">/*5、createPortal方法用于将节点添加到一个新容器中</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line">ReactDOM.createPortal(<span class="xml"><span class="tag">&lt;<span class="name">p</span>&gt;</span> Hello World <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span>, <span class="built_in">document</span>.querySelector(<span class="string">"#box"</span>));</span><br><span class="line"><span class="comment">//App组件的内容在#root中渲染，但App的子组件Child希望在#box中渲染，这是就需要createPortal方法来设置渲染容器。</span></span><br></pre></td></tr></table></figure>

<h2 id="React视图渲染"><a href="#React视图渲染" class="headerlink" title="React视图渲染"></a>React视图渲染</h2><p>当需要用React创建虚拟DOM时，React提供了方法createElement()。属于React对象，type为要创建的标签类型，config参数是设置生成的节点的相关属性，children代表该元素的内容或者子元素。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">React.createElement(type, config, children);</span><br><span class="line"></span><br><span class="line"><span class="comment">//config的类型是一个纯对象，其有两个固定参数key、ref，不能乱用。</span></span><br><span class="line">React.create(<span class="string">"h1"</span>, &#123;</span><br><span class="line">    id: <span class="string">"title"</span>,</span><br><span class="line">    className: <span class="string">"title"</span>,</span><br><span class="line">    title: <span class="string">"前端笔记"</span>,</span><br><span class="line">    style: &#123;</span><br><span class="line">        width: <span class="string">"100px"</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">/*children代表该元素的内容或者子元素，有三种写法</span></span><br><span class="line"><span class="comment">1、children为字符串时，代表在元素中添加文本内容；</span></span><br><span class="line"><span class="comment">2、children为数组时，会将数组中内容展开放入元素；</span></span><br><span class="line"><span class="comment">3、children为ReactElement时，会直接当作元素的子节点进行添加</span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure>

<p>上面直接用createElement()来编写视图的方式，从层级结构上十分不清晰，这里我们可以用JSX的方式来做代码编写。但浏览器并不认识JSX的语法扩展，所以需要借助babel.js来对JSX进行编译，使其称为浏览器识别的语法，就是React.createElement()</p>
<h3 id="JSX"><a href="#JSX" class="headerlink" title="JSX"></a>JSX</h3><p>JSX本身是一个值，这个值是一个ReactElement，而非字符串，如果给一个字符串的值，则会被解析为文本内容。因为JSX在解析的时候会被编译，字符串内容会进行转义。</p>
<p>插值表达式：为了让视图与数据进行绑定，就需要使用插值表达式</p>
<p>插值表达式注意事项如下：1、{}中接收一个JS表达式，可以为运算符、变量或函数调用。表达式代表其一定有值返回，而插值代表把表达式计算的值插入至视图；2、{}中接收的是函数调用时，该函数需要有返回值；3、字符串、数字会原样输出；布尔值、空、未定义输出空。但不会有错；数组支持直接输出，默认情况下会将数组连接符，替换成空，然后直接输出；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//特殊渲染情况：1、列表渲染，将数据批量渲染到JSX，这里需要设置key，确保diff算法</span></span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">"列表1"</span>, <span class="string">"列表2"</span>, <span class="string">"列表3"</span>];</span><br><span class="line">ReactDOM.render(<span class="xml"><span class="tag">&lt;<span class="name">ul</span>&gt;</span>&#123; arr.map(item =&gt; <span class="tag">&lt;<span class="name">li</span>&gt;</span>&#123;item&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span>) &#125;<span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span>, <span class="built_in">document</span>.getElementById(<span class="string">"root"</span>));</span><br><span class="line"><span class="comment">//2、条件渲染：但插值中不能用if语句，这时需要用&amp;&amp;或者||或者三目运算符或者利用判断函数。</span></span><br></pre></td></tr></table></figure>

<p>属性书写：1、所有属性名都使用驼峰命名法；2、如果属性值是字符串并且固定不变，则可以直接写；如果属性值为动态的，则需要用插值表达式；3、有一些特殊的属性名并不能直接用，例如class属性改为className，for属性改为htmlFor，colspan属性改为colSpan。4、style在书写时需注意，它接收的值是一个对象。 </p>
<p>JSX注意事项：1、浏览器并不支持JSX，在使用时需要babel编译；2、JSX不要写成字符串，否则标签会被当做文本直接输出；3、JSX是一个值，在输出时只能有一个顶层标签；3、所有的标签名字都必须小写；4、无论单标签还是双标签，都必须闭合；5、JSX并不是HTML，属性值必须使用驼峰1写法，且style接收的是对象。</p>
<h3 id="组件"><a href="#组件" class="headerlink" title="组件"></a>组件</h3><p>在React中编写组件主要有两种，一种是类组件，一种是函数组件。这里先看看类组件，类组件必须继承自React Component，并且必须有render方法，在render方法中的return中定义的是要渲染的视图。</p>
<p>这里我们介绍下React.StrictMode：用于检查项目中是否有潜在风险的检测工具，类似于严格模式，和Fragment类似，不会渲染任何真实DOM，只是为后代元素触发额外的检查和警告。只在开发模式下运行，能进行以下检测：1、识别具有不安生命周期的组件；2、有关旧式字符串ref用法的警告；3、关于已弃用的findDOMNode用法的警告；4、检测意外的副作用；5、检测遗留的context API。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React <span class="keyword">from</span> <span class="string">'react'</span></span><br><span class="line"><span class="keyword">import</span> ReactDOM <span class="keyword">from</span> <span class="string">'react-dom'</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span></span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span> Hello React <span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">ReactDOM.render(</span><br><span class="line">	&lt;React.StrictMode&gt;</span><br><span class="line">		&lt;App /&gt;</span><br><span class="line">	&lt;<span class="regexp">/React.StrictMode&gt;,</span></span><br><span class="line"><span class="regexp">	document.getElementById('root'));</span></span><br></pre></td></tr></table></figure>

<p>组件的props属性专门用于接收从父组件传递过来的数据，在调用组件时，可以把想传递进去的数据直接加在组件的属性上，然后在组件的内部，通过this.props属性接收传递进来的数据。与Vue类似，就不赘述了。</p>
<h4 id="State"><a href="#State" class="headerlink" title="State"></a>State</h4><p>在React中，组件就是一种状态机，组件会根据状态的不同输出不同UI，state是组件属性的一个属性，该属性的值是一个对象，可以直接用ES6的简写定义，或者要在constructor函数中定义时，由于组件继承自Component，需要加上super()；且constructor的第一个参数是调用该组件时传入的props，需要传递给super，在父类Component中加到this上。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">React</span>.<span class="title">Component</span></span>&#123;</span><br><span class="line">    <span class="comment">//state = &#123;name: "Hello World"&#125;;这是简写形式</span></span><br><span class="line">    <span class="keyword">constructor</span>(props) &#123;</span><br><span class="line">        <span class="keyword">super</span>(props);</span><br><span class="line">        <span class="keyword">this</span>.state = &#123;</span><br><span class="line">            name: <span class="string">"Hello World"</span></span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">let</span> &#123;name, age&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">        <span class="keyword">return</span> (<span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">               <span class="tag">&lt;<span class="name">p</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">               <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>修改state切记不能直接去赋值，因为该操作不会引起视图渲染，需要利用this.setState方法用于更新state的值。调用setState方法后，会根据其传入的值，对state进行修改，根据修改后的state生成新得虚拟DOM。</p>
<p>setState方法在被调用时，只接收两种参数类型，1、为对象，根据对象中的属性，去修改state对应的属性；2、函数，该函数必须有返回值，返回值为对象，代表要修改state中的哪一项。</p>
<p>注意事项：1、调用setState，只需要传入要修改的状态，不需要传入全部状态，setState会自动进行合并；2、setState是一个异步方法，调用后立刻打印会发现state并未改变，跟组件生命周期有关；3、多个setState会被合并，但只引起一次视图渲染，这是为了优化性能，避免多次操作DOM。</p>
<h4 id="通信"><a href="#通信" class="headerlink" title="通信"></a>通信</h4><p>React是单向数据流的设计，信息只能从父级向子级一层层向下传递。1、父级向子级进行通信：利用props即可；2、子组件向父级传递：单项数据流无法从子组件直接传递信息给父组件，但是可以在父组件定义好回调之后，把回调传递给子组件，利用回调向父级传递信息；3、同级组件间传递：同样需要在父组件上进行控制，在父组件上定义好相关的回调，然后传递给子组件，而子组件通过父组件的hi掉进行相关信息传递。</p>
<p>正是由于父组件往往需要管理庞大的数据，react hooks的方式慢慢演进出来。</p>
<p>跨组件通信：在React中，组件嵌套过多的情况，传递数据将变成麻烦的事，在React中专门提供了context用于解决跨组件通信；该用法常见于第三方库。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">调用createContext方法会返回两个组件Provider和Consumer，</span></span><br><span class="line"><span class="comment">Provider组件用于向其后代组件传递数据，把需要传递给子级的数据加给Provider的value属性即可向下传递；</span></span><br><span class="line"><span class="comment">Consumer组件用于接收父祖级传递下来的数据，在Consumer中可以编写一个函数，父祖级传递下来的数据会以参数的形式传递给该函数。</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//新建src/context.js文件，在context.js中新建context并导出context、Provider、Consumer:</span></span><br><span class="line"><span class="keyword">import</span> &#123;createContext&#125; <span class="keyword">from</span> <span class="string">"react"</span>;</span><br><span class="line"><span class="keyword">const</span> context = createContext();</span><br><span class="line"><span class="keyword">const</span> &#123;Provider, Consumer&#125; = context;</span><br><span class="line"><span class="keyword">export</span> &#123;Provider, Consumer&#125;;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> context;</span><br><span class="line"></span><br><span class="line"><span class="comment">//修改src/APP.js，在APP组件中调用Provider来传递要传递给子级的数据:</span></span><br><span class="line"><span class="keyword">import</span> React, &#123;Component&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> Child <span class="keyword">from</span> <span class="string">"./child"</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;Provider&#125; <span class="keyword">from</span> <span class="string">"./context"</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            &lt;Provider value-&#123;&#123;<span class="attr">info</span>:要传递给子级的数据&#125;&#125;&gt;</span><br><span class="line">                &lt;Child /&gt;</span><br><span class="line">            &lt; <span class="regexp">/Provider&gt;</span></span><br><span class="line"><span class="regexp">        &#125;</span></span><br><span class="line"><span class="regexp">    &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">export default App;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">/</span><span class="regexp">/新建src/</span>child.js文件，在Child中通过Consumer接收父级传递下来的数据</span><br><span class="line"><span class="keyword">import</span> React, &#123;Component&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;Consumer&#125; <span class="keyword">from</span> <span class="string">'./context'</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Child</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> (</span><br><span class="line">        &lt;Consumer&gt; &#123;(val) =&gt; &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;val.info&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">        &#125;&#125; &lt;<span class="regexp">/Consumer&gt;)</span></span><br><span class="line"><span class="regexp">    &#125;</span></span><br><span class="line"><span class="regexp">&#125;</span></span><br><span class="line"><span class="regexp">export default Child;</span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp"></span></span><br><span class="line"><span class="regexp">/</span>*在类组件中，除了通过Consumer来接收Provider传递过来的信息之外，还可以通过类的contextType来接收。</span><br><span class="line">然后在组件的context属性中就可以获取到Provider传递过来的数据</span><br><span class="line">*<span class="regexp">/</span></span><br><span class="line"><span class="regexp">import React, &#123;Component&#125; from 'react';</span></span><br><span class="line"><span class="regexp">import context from "./</span>context<span class="string">";</span></span><br><span class="line"><span class="string">class Child extends Component &#123;</span></span><br><span class="line"><span class="string">    static contextType = context;</span></span><br><span class="line"><span class="string">    render() &#123;</span></span><br><span class="line"><span class="string">        return &lt;p&gt;&#123;this.context.info&#125;&lt;/p&gt;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">export default Child;</span></span><br></pre></td></tr></table></figure>

<h4 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h4><p>生命周期指的是组件从创建到卸载的一个完整过程；React组件的生命周期分为三个阶段：1、挂载阶段：从组件的初始化开始，一直到组件创建完成并渲染到真实的DOM中；2、更新阶段：从组件开始更新，一直监测到组件更新完成并重新渲染完DOM；3、卸载阶段：监听组件从DOM中卸载。</p>
<p>挂载阶段：生命周期顺序为：constructor -&gt; getDerivedStateFromProps -&gt; componentWillMount -&gt; render -&gt; componentDidMount。</p>
<p>1、constructor(props)，在constructor中会初始化该组件；组件为继承Component类，需要加上super；</p>
<p>2、static getDerivedStateFromProps(props, state)：该方法用于从props中获取state，在挂载阶段获取数据并根据props来对state进行修改；其为静态方法，因此不能使用this；且必须有返回值，其返回值是对state的修改，相当于其他地方调用this.setState()时传入的修改对象。该生命周期是根据props来修改state，所以组件初始时一定要定义state属性。</p>
<p>3、componentWillMount：代表组件即将挂载；</p>
<p>4、render：render方法会根据return中的值生成虚拟DOM，然后提交给ReactDOM，渲染真实DOM。</p>
<p>5、componentDidMount：此时组件已经挂载完毕，虚拟DOM已经添加到真实DOM中。</p>
<p>更新阶段：有三个不同的过程，父组件更新引起的当前组件更新 -&gt; 当前组件自己更新 -&gt; forceUpdate。</p>
<p>父组件更新引起的当前组件更新：getDerivedStateFromProps -&gt; shouldComponentUpdate -&gt; render -&gt; getSnaphotBeforeUpdate -&gt; componentDidUpdate</p>
<p>1、getDerivedStateFromProps在更新阶段可以获取到新的props和state，同样返回值是要对state做的修改；</p>
<p>2、shouldComponentUpdate判断组件是否更新；3、render生成新的虚拟DOM；</p>
<p>4、getSnaphotBeforeUpdate：该方法执行在render生成虚拟DOM之后，渲染真实DOM之前，用于获取渲染前的DOM快照。其中的state、props都已经更新，且必须有返回值，该返回值会传递给componentDidUpdate</p>
<p>5、componentDidUpdate：接收getSnaphotBeforeUpdate传递过来的信息。</p>
<p>组件自己更新时，即在组件内部调用了setState，引起当前组件更新。shouldComponentUpdate -&gt; render -&gt; getSnaphotBeforeUpdate -&gt; componentDidUpdate</p>
<p>forceUpdate：React的强制更新组件的方式，当组件依赖的数据不是state时，数据改变了，此时希望视图也进行改变，就可以调用forceUpdate方法。forceUpdate会强制进行视图更新，不会再调用shouldComponentUpdate。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> name = <span class="string">"name"</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">            <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">        	&lt;button onClick=&#123;()=&gt; &#123;</span><br><span class="line">                name = "新名字";</span><br><span class="line">                this.forceUpdate();</span><br><span class="line"><span class="xml">            &#125;&#125;&gt;更新名字<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">			<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>卸载阶段：组件卸载即把组件从DOM中删除，其生命周期只有componentWillUnmount，用于监听组件即将卸载</p>
<p><img src="/../../image/react%E5%B7%A5%E7%A8%8B%E5%B8%88%E4%BF%AE%E7%82%BC%E6%8C%87%E5%8D%97/image-20211106174458546.png" alt="image-20211106174458546"></p>
<h4 id="ref"><a href="#ref" class="headerlink" title="ref"></a>ref</h4><p>在遇到特殊需求的时候，我们往往需要自己去操作原生DOM节点，比如让文本框获得焦点、使用一些第三方库等。在React中，除了getElementById等原生方法之外，还可以使用React提供的特殊API:ref。</p>
<p>常用的是string ref：当ref绑定在组件上，渲染完成后就可以获取组件实例，通过this.refs来获取。（使用方式时与Vue一致，不再赘述）使用时需要有一些注意事项：1、ref命名需要遵循驼峰命名法；2、单个组件内ref不能重名；3、获取ref时，要在componentDidUpdate和componentDidMount中进行，否则ref是还没有赋值或还没有更新的。</p>
<p>React16之后新增createRef方法：使用createRef创建ref时，需要先把ref绑定在组件的属性或变量中，然后和节点做绑定。获取ref时，需要通过ref的current属性来获取ref中具体的存储内容，示例代码如下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Child</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">p</span>&gt;</span> 子组件内容<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    parent = createRef();</span><br><span class="line">    child = createRef();</span><br><span class="line">    componentDidMount()&#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">this</span>.parent.current);<span class="comment">//打印真实的DOM节点</span></span><br><span class="line">        <span class="built_in">console</span>.log(<span class="keyword">this</span>.child.current);</span><br><span class="line">    &#125;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">            <span class="tag">&lt;<span class="name">p</span> <span class="attr">ref</span>=<span class="string">&#123;this.parent&#125;</span>&gt;</span>父组件内容<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">        	<span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;this.child&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="xml">          <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h4><p>react使用的是一种合成事件机制而非原生的DOM事件，事件名需遵循驼峰命名法，React通过JSX的插值放入一个函数。</p>
<p>其事件处理函数的this默认为undefined，如果希望修改this的化，则需要用bind绑定，或者使用箭头函数。在React中阻止默认事件不能使用return false，必须使用event.preventDefault。</p>
<h4 id="受控组件"><a href="#受控组件" class="headerlink" title="受控组件"></a>受控组件</h4><p>Html中，当用户对表单元素进行操作时，会改变表单的一些内部属性，（如:value、checked、selected等）；这些内部属性也就是该表单控件的一种状态。在React中，想要获取表单的内部状态或者想要控制表单的这些内部状态，可以把组件和表单的状态进行绑定，当组件的state改变时修改表单的状态，或者表单的状态被改变时，再通过setState修改组件的状态，这样九形成了组件对表单控件的控制。</p>
<p>这种操作在React中被称为受控组件。（在Vue上，本质上就是双向绑定）</p>
<p>下面我们以单选框和复选框为例，此控件需要控制checked属性。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    state = &#123;</span><br><span class="line">        checked: <span class="literal">false</span></span><br><span class="line">    &#125;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">let</span> &#123;checked&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line">            &lt;input type="checkbox"</span><br><span class="line">        	checked=&#123;checked&#125;</span><br><span class="line">        	onChange = &#123;(e) =&gt; &#123;</span><br><span class="line">                this.setState(&#123;</span><br><span class="line">                    checked: e.target.checked;</span><br><span class="line">                &#125;)</span><br><span class="line">            &#125;&#125;</span><br><span class="line"><span class="xml">    		/&gt;</span></span><br><span class="line"><span class="xml">		<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">上述案例演示的是受控组件的做法，组件的state和表单控件的状态实时同步。</span></span><br><span class="line"><span class="comment">如果希望state和表单控件的初始值一致，而非实时同步，则可以写成非受控组件的形式</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    state = &#123;</span><br><span class="line">        val:<span class="string">""</span>,</span><br><span class="line">        checked: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">let</span> &#123;val, checked&#125; = <span class="keyword">this</span>.state;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">defaultValue</span>=<span class="string">&#123;val&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="xml">            <span class="tag">&lt;<span class="name">input</span> <span class="attr">type</span>=<span class="string">"checkbox"</span> <span class="attr">defaultChecked</span>=<span class="string">&#123;checked&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="xml">                <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//编写非受控组件时，无须添加onChange事件，但也要注意value和checked，要写defaultValue和defaultChecked</span></span><br></pre></td></tr></table></figure>

<p>children属性时React中一个特殊的API，主要用于传递组件内部要渲染的内容，比如要编写一个弹窗组件，但弹窗的内容是未知的，或者是调用时才需要传递的，这时候就饿可以使用children属性，将弹窗的内容传递进组件。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Popup</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">let</span> &#123;title, children&#125; = <span class="keyword">this</span>.props;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">"popup"</span>&gt;</span></span></span><br><span class="line"><span class="xml">            <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;title&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="xml">        	<span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">App</span> <span class="keyword">extends</span> <span class="title">Component</span> </span>&#123;</span><br><span class="line">    render() &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">Popup</span> <span class="attr">title</span>=<span class="string">"自定义弹窗"</span>&gt;</span></span></span><br><span class="line"><span class="xml">            	<span class="tag">&lt;<span class="name">div</span>&gt;</span>弹窗内容<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        	<span class="tag">&lt;/<span class="name">Popup</span>&gt;</span></span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//children的用法，在调用时，把要传递的内容写在标签对之间，在子组件中通过props.children就可以接收到父组件传递过来的要渲染的内容</span></span><br></pre></td></tr></table></figure>

<p>dangerouslySetInnerHTML:可以帮助开发者在React元素中直接添加HTML元素，其接收的是一个对象，在对象的html属性中去写要设置的InnerHTML。在React中，如果直接插入带标签的字符串，会把标签也解析为内容的一部分，而不是解析成一个正常的HTML标签。</p>
<h2 id="ReactHooks"><a href="#ReactHooks" class="headerlink" title="ReactHooks"></a>ReactHooks</h2><p>函数式组件即一个函数就是一个组件，函数的第一个参数是父级传递进来的props，返回值是组件要输出的JSX视图。</p>
<p>ReactHooks是React16之后新增的特性：在使用类组件时，会有大量的业务逻辑（如各类接口请求）放在componentDidMount和componentDidUpdate等生命周期函数中，这样会使组件变得复杂且难以维护；且很多逻辑难以通过组件的方式进行复用。</p>
<h3 id="常用Hooks"><a href="#常用Hooks" class="headerlink" title="常用Hooks"></a>常用Hooks</h3><p>Hooks的本质是一类特殊的函数，在React中除了可以自定义Hook以外，还提供了很多内置的Hook。</p>
<p>（1）、useState</p>
<p>useState 可以帮助开发者在函数式组件中使用state，调用该方法时传入state的初始值，该方法会返回一个数组，数组的第0位是state的具体值，而第1位是修改该state的方法，同类组件的setState方法一样，调用该方法会更新State，然后引起视图更新。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//常用方式</span></span><br><span class="line"><span class="keyword">const</span> [state, setState] = useState(initialState);</span><br><span class="line"></span><br><span class="line"><span class="comment">//示例代码 &#123;useState&#125;是模块的命名导出,不加&#123;&#125;的是默认导出 </span></span><br><span class="line"><span class="keyword">import</span> React, &#123;useState&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> [name, setName] = useState(<span class="string">"kkb"</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">    	&lt;button onClick=&#123;()=&gt;&#123;</span><br><span class="line">            setName("hello")</span><br><span class="line"><span class="xml">        &#125;&#125;&gt;显示全称<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> App;</span><br></pre></td></tr></table></figure>

<p>在使用useState时，有下面三个问题要注意：1、useState返回的setState方法同类组件的setState一样，也是一个异步方法，需要组件更新之后state的值才会变成新值；2、useState返回的setState并不具有类组件的setState合并多个state的作用，如果state中有多个state，在更新时则会一起更新；因此我们使用时需要注意只修改某个值则最好用解构赋值。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> [data, setData] = useState(&#123;<span class="attr">name</span>:<span class="string">"kkb"</span>, <span class="attr">age</span>:<span class="string">"10"</span>&#125;);</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;data.name&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">    	&lt;button onClick=&#123;() =&gt; &#123;</span><br><span class="line">            setData(&#123;...data,name:"hello"&#125;)</span><br><span class="line"><span class="xml">        &#125;&#125;&gt;显示全称<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">		<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>3、同一个组件中可以使用useState创建多个state；</p>
<p>（2）、useRef</p>
<p>useRef可以看成是createRef的Hook版，使用时先把ref存入变量，然后变量＆DOM节点绑定，使用时通过ref的current属性来获取DOM节点。在函数式组件中，useRef除了用来绑定DOM节点外，还用来保存跨渲染周期的数据，也就是获取组件渲染之前的数据；具体会与useEffect一起使用。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123;useRef&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> elP = useRef();</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">ref</span>=<span class="string">&#123;elP&#125;</span>&gt;</span>hello world<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line">    	&lt;button onClick=&#123;()=&gt; &#123;</span><br><span class="line">            console.log(elP.cuurent);</span><br><span class="line"><span class="xml">        &#125;&#125;&gt;显示全称<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> App;</span><br></pre></td></tr></table></figure>

<p>（3）、useEffect</p>
<p>effect翻译为专业术语为副作用，网络请求、DOM操作都是副作用的一种，useEffect就是专门用于处理副作用的。在类组件中，副作用通常在componentDidMount和componentDidUpdate中进行处理，而useEffect就相当于componentDidMount、componentDidUpdate和componentWillUnmount的集合体。useEffect包括两个参数执行时的回调函数和依赖参数，并且回调函数还有一个返回函数。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> React, &#123;useState, useEffect&#125; <span class="keyword">from</span> <span class="string">'react'</span>;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Course</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> [course, setCourse] = useState(<span class="string">"课程"</span>);</span><br><span class="line">    <span class="keyword">const</span> [num, setNum] = useState(<span class="number">1</span>);</span><br><span class="line">    useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"组件挂载或更新"</span>)；</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">"清理更新前的一些全局内容，清理时在副作用函数执行之前执行；或检测组件即将卸载"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, [num]);<span class="comment">//只有num更新时才会执行回调函数</span></span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line">        &lt;div&gt;选择课程：</span><br><span class="line">    	&lt;select value=&#123;course&#125;</span><br><span class="line">    	onChange=&#123;(&#123;target&#125;) =&gt; &#123; setCourse(target.value);&#125;&#125;&gt;</span><br><span class="line">        &lt;option value="课程1"&gt;课程1&lt;/option&gt;</span><br><span class="line">		&lt;option value="课程2"&gt;课程2&lt;/option&gt;</span><br><span class="line">		&lt;/select&gt;</span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">	&lt;div&gt;购买数量：</span><br><span class="line">		&lt;input type="number" value=&#123;num&#125; min=&#123;1&#125;</span><br><span class="line">				onChange=&#123;(&#123;target&#125;) =&gt; &#123; setNum(target.value);&#125;&#125;</span><br><span class="line">          /&gt;</span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> [show, setShow] = useState(<span class="literal">true</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">        &#123;show?<span class="tag">&lt;<span class="name">Course</span> /&gt;</span>:""&#125;</span></span><br><span class="line">    	&lt;button onClick=&#123;()=&gt; &#123; setShow(!show);&#125;&#125;&gt;</span><br><span class="line">            &#123;show?"隐藏课程"："显示课程"&#125;</span><br><span class="line"><span class="xml">		<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> App;</span><br></pre></td></tr></table></figure>

<p>useEffect的两个参数：执行时的回调函数、依赖参数。依赖参数其本身是一个数组，在数组中放入要依赖的数据，当这些数据有更新时，就会执行回调函数。整个组件的生命周期流程如下：</p>
<p>组件挂载 -&gt; 执行副作用(回调函数) -&gt; 组件更新 -&gt; 执行清理函数(useEffect返回的函数) -&gt; 执行副作用(回调函数) -&gt; 组件准备卸载 -&gt; 执行清理函数(返还函数) -&gt; 组件卸载。</p>
<p>useEffect就相当于componentDidMount、componentDidUpdate和componentWillUnmount的集合体，如果只想单纯在挂载后、更新后、卸载前其中之一阶段执行，可以参考以下操作：</p>
<p>componentDidMount、componentWillUnmount：想只在挂载后或卸载前执行，可以把依赖参数置为空；这样仅在挂载时触发副作用函数，仅在卸载前执行副作用的返回函数。</p>
<p>componentDidUpdate：只检测更新相对比较麻烦，需要区分更新还是挂载需要检测依赖数据与初始值是否一致。这种情况需要借助useRef的原因在于:ref如果和数据绑定的话，数据更新时ref并不会自动更新，这样就可以获取到更新前的数据。下例代码中，可以看到useEffect模拟的各个阶段，且一个组件中拥有多个useEffect的实例。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">Course</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> [course, setCourse] = useState(<span class="string">"Hello"</span>);</span><br><span class="line">    <span class="keyword">const</span> [num, setNum] = useState(<span class="number">1</span>);</span><br><span class="line">    <span class="keyword">let</span> prevCourse = useRef(course);</span><br><span class="line">    <span class="keyword">let</span> prevNum = useRef(num);</span><br><span class="line">    useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"组件挂载阶段"</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">"组件卸载之前"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, []);<span class="comment">//传入依赖参数为空数组，这样就只会在挂载和卸载阶段调用，而组件更新阶段不会调用该副作用</span></span><br><span class="line">    useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (course != prevCourse.current || num != prevNum.current) &#123;</span><br><span class="line">            <span class="comment">//如果当前值和上次值不一样，则待变组件有更新</span></span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">"组件更新"</span>);</span><br><span class="line">            <span class="comment">//这里注意，ref不会自动进行更新，需要手动更新</span></span><br><span class="line">            prevCourse.current = course;</span><br><span class="line">            prevNum.current = num;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, [course, num]);</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line">        &lt;div&gt;选择课程：</span><br><span class="line">    	&lt;select value= &#123;course&#125; onChange=&#123;(&#123;target&#125;) =&gt; &#123;</span><br><span class="line">            setCourse(target.value);</span><br><span class="line">        &#125;&#125;&gt; </span><br><span class="line">        	&lt;option value="课程1"&gt;课程1&lt;/option&gt;</span><br><span class="line">			&lt;option value="课程2"&gt;课程2&lt;/option&gt;</span><br><span class="line">		&lt;/select&gt;</span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">	&lt;div&gt;购买数量:</span><br><span class="line">    	&lt;input type="number" value=&#123;num&#125; min=&#123;1&#125;</span><br><span class="line">				onChange=&#123;(&#123;target&#125;) =&gt; &#123;</span><br><span class="line">                    setNum(target.value);</span><br><span class="line">                &#125;&#125;</span><br><span class="line">                /&gt;</span><br><span class="line"><span class="xml">		<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="xml">	<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="自定义Hook"><a href="#自定义Hook" class="headerlink" title="自定义Hook"></a>自定义Hook</h3><p>Hooks的使用规则如下：1、只能在函数式组件和自定义Hooks之中调用Hooks，普通函数或者类组件中不能使用Hooks；2、只能在函数的第一层调用Hooks。如果函数中还嵌套了流程控制语句如if或者for，这些地方是不能再调用Hooks的，当然函数之中嵌套了子函数，子函数中一样也不能使用Hooks。Hooks的设计极度依赖其定义时的顺序，如组件更新时Hooks的调用顺序改变了，就会出现不可预知的问题。Hooks的使用也是为了保证Hooks调用顺序的稳定性，为此React提供一个ESLint plugin来做静态代码检测。</p>
<p>除了可以使用React定义好的Hooks之外，React也允许开发者自己定义Hook。前文说过一些简单的单纯逻辑难以通过组件去复用，而自定义Hook就可以很完美地去解决这个问题。可以把一些二需要重复使用的逻辑自定义成Hook。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">useScrollY</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> [scrollY, setScrollY] = useState(<span class="number">0</span>);</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">scroll</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">        setScrollY(<span class="built_in">window</span>.scrollY);</span><br><span class="line">    &#125;</span><br><span class="line">    useEffect(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">        <span class="built_in">window</span>.addEventListener(<span class="string">"scroll"</span>, scroll);</span><br><span class="line">        <span class="keyword">return</span> <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">            <span class="built_in">window</span>.removeEventListener(<span class="string">"scroll"</span>, scroll);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, []);</span><br><span class="line">    <span class="keyword">return</span> scrollY;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">App</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    <span class="keyword">let</span> scrollY = useScrollY();</span><br><span class="line">    <span class="keyword">return</span> <span class="xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;border:</span>"<span class="attr">1px</span> <span class="attr">solid</span> #<span class="attr">000</span>", <span class="attr">height:</span>"<span class="attr">1500px</span>"&#125;&#125;&gt;</span></span></span><br><span class="line"><span class="xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">style</span>=<span class="string">&#123;&#123;position:</span>"<span class="attr">fixed</span>", <span class="attr">left:0</span>, <span class="attr">top:0</span>&#125;&#125;当前滚动条未知是：&#123;<span class="attr">scrollY</span>&#125;&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="xml">		<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上例中useScrollY就是一个自定义Hook，在useScrollY中，定义好了顿东条未知的整个逻辑处理，将来需要使用滚动条位置时，直接调用useScrollY即会返回一个状态，并且该状态会根据滚动条的位置变化而进行更新。</p>
<p>这样就可以极大第精简组件的逻辑，使组件中的代码一目了然，并且其他地方有响应需求也可以重复调用useScrollY。在使用自定义Hook时，同样需要遵守Hooks的使用规则，另外要注意React要求自定义Hook的命名也必须以use开始，以区别于其他函数。</p>

      
    </div>
    
    
    
    
    <div>
      
    </div>
      <footer class="post-footer">
        <div class="post-eof"></div>
      </footer>
  </article>
  
  
  


  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/2/"><i class="fa fa-angle-left" aria-label="上一页"></i></a><a class="page-number" href="/">1</a><a class="page-number" href="/page/2/">2</a><span class="page-number current">3</span><a class="page-number" href="/page/4/">4</a><span class="space">&hellip;</span><a class="page-number" href="/page/10/">10</a><a class="extend next" rel="next" href="/page/4/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="Li Yudong"
      src="/images/author.jpg">
  <p class="site-author-name" itemprop="name">Li Yudong</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">95</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">18</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>
  <div class="links-of-author motion-element">
      <span class="links-of-author-item">
        <a href="https://github.com/tiarmor1" title="GitHub → https:&#x2F;&#x2F;github.com&#x2F;tiarmor1" rel="noopener" target="_blank"><i class="fab fa-github fa-fw"></i>GitHub</a>
      </span>
      <span class="links-of-author-item">
        <a href="mailto:1157019137@qq.com" title="E-Mail → mailto:1157019137@qq.com" rel="noopener" target="_blank"><i class="fa fa-envelope fa-fw"></i>E-Mail</a>
      </span>
  </div>
  <div class="cc-license motion-element" itemprop="license">
    <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" class="cc-opacity" rel="noopener" target="_blank"><img src="/images/cc-by-nc-sa.svg" alt="Creative Commons"></a>
  </div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 2020 – 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Li Yudong</span>
</div>

        








      </div>
    </footer>
  </div>

  
  
  <script color='0,0,0' opacity='0.5' zIndex='-1' count='150' src="/lib1/canvas-nest/canvas-nest.min.js"></script>
  <script src="/lib1/anime.min.js"></script>
  <script src="/lib1/velocity/velocity.min.js"></script>
  <script src="/lib1/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/pisces.js"></script>


<script src="/js/next-boot.js"></script>




  




  
<script src="/js/local-search.js"></script>













  

  

  
   <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
   <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script> 
   <script type="text/javascript" src="/js/fireworks.js"></script>
  
</body>
</html>

